builder: fix SIGSEGV when stripping duplicate function definitions

The previous approach of erasing basic blocks one-by-one from
duplicate function definitions could crash with a segfault. When
a function has complex control flow (branches, switches, PHI nodes),
deleting a basic block that is still referenced by instructions in
other blocks of the same function causes use-after-free in LLVM.

LLVM's C++ Function::deleteBody() avoids this by calling
dropAllReferences() on all blocks first, but that API is not
exposed in the Go LLVM bindings.

Fix this by using a completely different approach: instead of
manually deleting the function body, set the duplicate function's
linkage to LinkOnceODRLinkage. This tells the LLVM linker that the
definition can be merged with another copy, causing it to prefer
the already-linked ExternalLinkage definition in the destination
module. The result is the same (the runtime's definition wins) but
without any unsafe block manipulation.
This commit is contained in:
deadprogram
2026-04-10 12:28:29 +02:00
committed by Ron Evans
parent ddacdfacd5
commit 98b3c27c76
+3 -4
View File
@@ -551,7 +551,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// body in a standard library package that was previously
// just a declaration provided by //go:linkname from the
// runtime. In that case, keep the existing (runtime)
// definition and turn the new one into a declaration.
// definition by weakening the new one's linkage so the
// LLVM linker discards it in favor of the existing one.
for fn := pkgMod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if fn.IsDeclaration() {
continue
@@ -560,9 +561,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if existing.IsNil() || existing.IsDeclaration() {
continue
}
for _, bb := range fn.BasicBlocks() {
bb.EraseFromParent()
}
fn.SetLinkage(llvm.LinkOnceODRLinkage)
}
err = llvm.LinkModules(mod, pkgMod)
if err != nil {