Skip to content

Commit 55b6141

Browse files
committed
fix(lib): opt+llc native codegen, pkgIRs, unified @llvm.used, stacktrace frame names
1 parent 5d42fdc commit 55b6141

6 files changed

Lines changed: 131 additions & 36 deletions

File tree

backend.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,36 @@ func optThinLtoPreLinkPass(optLevel string) string {
6969
}
7070
}
7171

72+
// compileIRToNativeObj compiles an LLVM IR `.ll` to a native ELF/Mach-O
73+
// relocatable object without LTO. Used by --lib mode where the caller
74+
// wants a plain .o for ld -r, not ThinLTO bitcode.
75+
// Pipeline: opt <optLevel> (middle-end) -> llc -filetype=obj (codegen).
76+
func compileIRToNativeObj(llPath, outPath, optLevel string) error {
77+
bcFile, err := os.CreateTemp("", "tin-lib-*.bc")
78+
if err != nil {
79+
return fmt.Errorf("cannot create temp bitcode file: %w", err)
80+
}
81+
bcPath := bcFile.Name()
82+
_ = bcFile.Close()
83+
defer func() { _ = os.Remove(bcPath) }()
84+
85+
optCmd := exec.Command("opt", optLevel, llPath, "-o", bcPath)
86+
optCmd.Stdout = os.Stdout
87+
optCmd.Stderr = os.Stderr
88+
if err := optCmd.Run(); err != nil {
89+
return fmt.Errorf("opt %s: %w", llPath, err)
90+
}
91+
92+
llcCmd := exec.Command("llc", "-filetype=obj", "--relocation-model=pic", bcPath, "-o", outPath)
93+
llcCmd.Stdout = os.Stdout
94+
llcCmd.Stderr = os.Stderr
95+
if err := llcCmd.Run(); err != nil {
96+
return fmt.Errorf("llc %s: %w", bcPath, err)
97+
}
98+
99+
return nil
100+
}
101+
72102
// compileIRToObj compiles an LLVM IR `.ll` input into ThinLTO bitcode.
73103
// The actual codegen happens in ld.lld at link time -- matches what
74104
// `clang -c -flto=thin` does internally, just bypassing the clang

codegen/codegen.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,10 @@ type CodeGen struct {
370370
// end of Generate materializes one global per module.
371371
llvmUsedRoots map[*ir.Module][]*ir.Global
372372

373+
// llvmUsedFuncs is the per-module list of functions to include in
374+
// the same combined @llvm.used emission (alongside llvmUsedRoots).
375+
llvmUsedFuncs map[*ir.Module][]*ir.Func
376+
373377
// monoMods holds dedicated content-addressed modules carrying
374378
// monomorphized fn bodies (step 5 of incremental compilation).
375379
// Keyed by mono_hash; populated by extractMonoModules during

codegen/interop.go

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -471,23 +471,9 @@ func (cg *CodeGen) emitInteropWrappers(stmts []ast.Node) error {
471471
// passes. The user's contract is "this function is callable from C";
472472
// linker DCE breaks that contract.
473473
func (cg *CodeGen) pinInteropWrappers(wrappers []*ir.Func) {
474-
if len(wrappers) == 0 {
475-
return
474+
for _, f := range wrappers {
475+
cg.registerLlvmUsedFunc(cg.mod, f)
476476
}
477-
478-
i8Ptr := irtypes.I8Ptr
479-
480-
entries := make([]constant.Constant, len(wrappers))
481-
for i, f := range wrappers {
482-
entries[i] = constant.NewBitCast(f, i8Ptr)
483-
}
484-
485-
arrTy := irtypes.NewArray(uint64(len(wrappers)), i8Ptr)
486-
init := constant.NewArray(arrTy, entries...)
487-
488-
used := cg.mod.NewGlobalDef("llvm.used", init)
489-
used.Linkage = enum.LinkageAppending
490-
used.Section = "llvm.metadata"
491477
}
492478

493479
// emitInteropWrapperFor emits a single wrapper. Assumes the validation
@@ -521,6 +507,16 @@ func (cg *CodeGen) emitInteropWrapperWithName(fn *ast.FuncDecl, wrapperName stri
521507
return cg.nodeErr(fn, "fn %s: #interop entry resolved to non-function value", fn.Name)
522508
}
523509

510+
// Override the pclntab display name so stacktrace() reports the
511+
// C-visible symbol (__tin_interop_<name>) rather than the Tin source
512+
// name. The heuristic would already return the IR name unchanged
513+
// (it starts with __tin_), but recordFnDisplayName stored the Tin
514+
// source name earlier; override it here.
515+
if cg.fnDisplayNames == nil {
516+
cg.fnDisplayNames = map[string]string{}
517+
}
518+
cg.fnDisplayNames[internalFn.Name()] = internalFn.Name()
519+
524520
// When the active emit target is a sibling module (CTFE shimMod),
525521
// internalFn lives in cg.mod and is unreachable from the wrapper's
526522
// blocks. Mirror it as a `declare` in the active module so calls
@@ -706,6 +702,7 @@ func (cg *CodeGen) emitInteropWrapperWithName(fn *ast.FuncDecl, wrapperName stri
706702
}
707703

708704
wrapper := cg.activeModule().NewFunc(wrapperName, retType, wrapperParams...)
705+
wrapper.FuncAttrs = append(wrapper.FuncAttrs, ir.AttrString("noinline"))
709706
block := wrapper.NewBlock("entry")
710707
// Skip the tin_runtime_init bootstrap when emitting into the CTFE
711708
// shim module: the dispatcher (Tin compiler) doesn't link the runtime

codegen/reflect_table.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,20 +137,47 @@ func (cg *CodeGen) registerLlvmUsed(mod *ir.Module, g *ir.Global) {
137137
cg.llvmUsedRoots[mod] = append(cg.llvmUsedRoots[mod], g)
138138
}
139139

140+
// registerLlvmUsedFunc appends f to mod's pending llvm.used funcs. Idempotent
141+
// per (module, function).
142+
func (cg *CodeGen) registerLlvmUsedFunc(mod *ir.Module, f *ir.Func) {
143+
if cg.llvmUsedFuncs == nil {
144+
cg.llvmUsedFuncs = map[*ir.Module][]*ir.Func{}
145+
}
146+
147+
for _, existing := range cg.llvmUsedFuncs[mod] {
148+
if existing == f {
149+
return
150+
}
151+
}
152+
153+
cg.llvmUsedFuncs[mod] = append(cg.llvmUsedFuncs[mod], f)
154+
}
155+
140156
// emitLlvmUsedRoots materializes the per-module @llvm.used global from
141-
// every root collected via registerLlvmUsed. Must be the LAST step in
142-
// codegen (after every emitter that would call registerLlvmUsed) so
143-
// nothing is missed and no two @llvm.used collide in the same module.
157+
// every root collected via registerLlvmUsed / registerLlvmUsedFunc. Must be
158+
// the LAST step in codegen so nothing is missed and no two @llvm.used collide
159+
// in the same module.
144160
func (cg *CodeGen) emitLlvmUsedRoots() {
145-
for mod, roots := range cg.llvmUsedRoots {
146-
if len(roots) == 0 {
147-
continue
148-
}
161+
mods := map[*ir.Module]struct{}{}
162+
for mod := range cg.llvmUsedRoots {
163+
mods[mod] = struct{}{}
164+
}
165+
for mod := range cg.llvmUsedFuncs {
166+
mods[mod] = struct{}{}
167+
}
149168

150-
used := make([]constant.Constant, 0, len(roots))
151-
for _, g := range roots {
169+
for mod := range mods {
170+
used := make([]constant.Constant, 0)
171+
for _, g := range cg.llvmUsedRoots[mod] {
152172
used = append(used, constant.NewBitCast(g, irtypes.I8Ptr))
153173
}
174+
for _, f := range cg.llvmUsedFuncs[mod] {
175+
used = append(used, constant.NewBitCast(f, irtypes.I8Ptr))
176+
}
177+
178+
if len(used) == 0 {
179+
continue
180+
}
154181

155182
usedArrTy := irtypes.NewArray(uint64(len(used)), irtypes.I8Ptr)
156183
usedInit := constant.NewArray(usedArrTy, used...)

main.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,20 +1628,48 @@ func compileIRWithPkgs(ir string, pkgIRs []namedIR, outBin string, libMode bool,
16281628

16291629
defer func() { _ = os.Remove(irObjName) }()
16301630

1631-
irArgs := append([]string{optLevel, "-c"}, clangTargetFlag()...)
1632-
irArgs = append(irArgs, llInputFile, "-o", irObjName)
1633-
clangIR := exec.Command("clang", irArgs...)
1634-
clangIR.Stdout = os.Stdout
1635-
clangIR.Stderr = os.Stderr
1636-
1637-
if err := clangIR.Run(); err != nil {
1631+
if err := compileIRToNativeObj(llInputFile, irObjName, optLevel); err != nil {
16381632
return err
16391633
}
16401634

16411635
objs := []string{irObjName}
16421636

16431637
var tmpObjs []string
16441638

1639+
for _, pkg := range pkgIRs {
1640+
pkgLL, err := os.CreateTemp("", "tin-pkg-*.ll")
1641+
if err != nil {
1642+
return fmt.Errorf("cannot create temp IR file: %w", err)
1643+
}
1644+
pkgLLName := pkgLL.Name()
1645+
if _, err := pkgLL.WriteString(pkg.irText); err != nil {
1646+
_ = pkgLL.Close()
1647+
_ = os.Remove(pkgLLName)
1648+
return err
1649+
}
1650+
_ = pkgLL.Close()
1651+
1652+
pkgObj, err := os.CreateTemp("", "tin-pkg-*.o")
1653+
if err != nil {
1654+
_ = os.Remove(pkgLLName)
1655+
return fmt.Errorf("cannot create temp object file: %w", err)
1656+
}
1657+
pkgObjName := pkgObj.Name()
1658+
_ = pkgObj.Close()
1659+
1660+
tmpObjs = append(tmpObjs, pkgLLName, pkgObjName)
1661+
1662+
if err := compileIRToNativeObj(pkgLLName, pkgObjName, optLevel); err != nil {
1663+
for _, f := range tmpObjs {
1664+
_ = os.Remove(f)
1665+
}
1666+
_ = os.Remove(pkgLLName)
1667+
return err
1668+
}
1669+
1670+
objs = append(objs, pkgObjName)
1671+
}
1672+
16451673
for _, cs := range cSources {
16461674
cObj, err := os.CreateTemp("", "tin-c-*.o")
16471675
if err != nil {

runtime/stacktrace.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,20 @@ static int32_t resolve_frame(uintptr_t ip, int spawn_of, int32_t flags) {
381381
written = snprintf(buf, sizeof buf, "\"%s%s:%s+0x%lx\"",
382382
spawn_pfx, lib, sym_name, off);
383383
}
384-
} else if (off == 0 && !spawn_of) {
385-
written = snprintf(buf, sizeof buf, "%s", sym_name);
386384
} else {
387-
written = snprintf(buf, sizeof buf, "\"%s%s+0x%lx\"",
388-
spawn_pfx, sym_name, off);
385+
// Main binary: include the binary name after @ so callers can
386+
// tell which binary the symbol belongs to without DWARF.
387+
// Produces "sym@binary+0x<off>" (analogous to pclntab's "sym@file:line").
388+
const char *fname = (have_dli && info.dli_fname) ? info.dli_fname : "";
389+
const char *fbase = strrchr(fname, '/');
390+
const char *bin = fbase ? fbase + 1 : fname;
391+
if (off == 0 && !spawn_of) {
392+
written = snprintf(buf, sizeof buf, "\"%s%s@%s\"",
393+
spawn_pfx, sym_name, bin);
394+
} else {
395+
written = snprintf(buf, sizeof buf, "\"%s%s@%s+0x%lx\"",
396+
spawn_pfx, sym_name, bin, off);
397+
}
389398
}
390399
} else {
391400
written = snprintf(buf, sizeof buf, "\"%s??+0x%lx\"",

0 commit comments

Comments
 (0)