Skip to content

Commit bbb2b0c

Browse files
committed
builder: use a separate module for command-line set strings
Strings that are set via the command line (as in -ldflags="-X ...") are now created in a separate module to avoid type renaming issues. LLVM sometimes renames types or merges types that are structurally the same, and putting these strings in a separate module avoids this issue (and lets llvm.LinkModules deal with the difference). This fixes #4810.
1 parent 4768c7d commit bbb2b0c

File tree

1 file changed

+40
-41
lines changed

1 file changed

+40
-41
lines changed

builder/build.go

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
449449
if global.IsNil() {
450450
return errors.New("global not found: " + globalName)
451451
}
452+
globalType := global.GlobalValueType()
453+
if globalType.TypeKind() != llvm.StructTypeKind || globalType.StructName() != "runtime._string" {
454+
// Verify this is indeed a string. This is needed so
455+
// that makeGlobalsModule can just create the right
456+
// globals of string type without checking.
457+
return fmt.Errorf("%s: not a string", globalName)
458+
}
452459
name := global.Name()
453-
newGlobal := llvm.AddGlobal(mod, global.GlobalValueType(), name+".tmp")
460+
newGlobal := llvm.AddGlobal(mod, globalType, name+".tmp")
454461
global.ReplaceAllUsesWith(newGlobal)
455462
global.EraseFromParentAsGlobal()
456463
newGlobal.SetName(name)
@@ -538,6 +545,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
538545
}
539546
}
540547

548+
// Insert values from -ldflags="-X ..." into the IR.
549+
// This is a separate module, so that the "runtime._string" type
550+
// doesn't need to match precisely. LLVM tends to rename that type
551+
// sometimes, leading to errors. But linking in a separate module
552+
// works fine. See:
553+
// https://github.com/tinygo-org/tinygo/issues/4810
554+
globalsMod := makeGlobalsModule(ctx, globalValues, machine)
555+
llvm.LinkModules(mod, globalsMod)
556+
541557
// Create runtime.initAll function that calls the runtime
542558
// initializer of each package.
543559
llvmInitFn := mod.NamedFunction("runtime.initAll")
@@ -590,7 +606,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
590606

591607
// Run all optimization passes, which are much more effective now
592608
// that the optimizer can see the whole program at once.
593-
err := optimizeProgram(mod, config, globalValues)
609+
err := optimizeProgram(mod, config)
594610
if err != nil {
595611
return err
596612
}
@@ -1145,7 +1161,7 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c
11451161
// optimizeProgram runs a series of optimizations and transformations that are
11461162
// needed to convert a program to its final form. Some transformations are not
11471163
// optional and must be run as the compiler expects them to run.
1148-
func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues map[string]map[string]string) error {
1164+
func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
11491165
err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA())
11501166
if err != nil {
11511167
return err
@@ -1163,12 +1179,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m
11631179
}
11641180
}
11651181

1166-
// Insert values from -ldflags="-X ..." into the IR.
1167-
err = setGlobalValues(mod, globalValues)
1168-
if err != nil {
1169-
return err
1170-
}
1171-
11721182
// Run most of the whole-program optimizations (including the whole
11731183
// O0/O1/O2/Os/Oz optimization pipeline).
11741184
errs := transform.Optimize(mod, config)
@@ -1182,10 +1192,19 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m
11821192
return nil
11831193
}
11841194

1185-
// setGlobalValues sets the global values from the -ldflags="-X ..." compiler
1186-
// option in the given module. An error may be returned if the global is not of
1187-
// the expected type.
1188-
func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error {
1195+
func makeGlobalsModule(ctx llvm.Context, globals map[string]map[string]string, machine llvm.TargetMachine) llvm.Module {
1196+
mod := ctx.NewModule("cmdline-globals")
1197+
targetData := machine.CreateTargetData()
1198+
defer targetData.Dispose()
1199+
mod.SetDataLayout(targetData.String())
1200+
1201+
stringType := ctx.StructCreateNamed("runtime._string")
1202+
uintptrType := ctx.IntType(targetData.PointerSize() * 8)
1203+
stringType.StructSetBody([]llvm.Type{
1204+
llvm.PointerType(ctx.Int8Type(), 0),
1205+
uintptrType,
1206+
}, false)
1207+
11891208
var pkgPaths []string
11901209
for pkgPath := range globals {
11911210
pkgPaths = append(pkgPaths, pkgPath)
@@ -1201,24 +1220,6 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro
12011220
for _, name := range names {
12021221
value := pkg[name]
12031222
globalName := pkgPath + "." + name
1204-
global := mod.NamedGlobal(globalName)
1205-
if global.IsNil() || !global.Initializer().IsNil() {
1206-
// The global either does not exist (optimized away?) or has
1207-
// some value, in which case it has already been initialized at
1208-
// package init time.
1209-
continue
1210-
}
1211-
1212-
// A strin is a {ptr, len} pair. We need these types to build the
1213-
// initializer.
1214-
initializerType := global.GlobalValueType()
1215-
if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" {
1216-
return fmt.Errorf("%s: not a string", globalName)
1217-
}
1218-
elementTypes := initializerType.StructElementTypes()
1219-
if len(elementTypes) != 2 {
1220-
return fmt.Errorf("%s: not a string", globalName)
1221-
}
12221223

12231224
// Create a buffer for the string contents.
12241225
bufInitializer := mod.Context().ConstString(value, false)
@@ -1229,22 +1230,20 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro
12291230
buf.SetLinkage(llvm.PrivateLinkage)
12301231

12311232
// Create the string value, which is a {ptr, len} pair.
1232-
zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false)
1233-
ptr := llvm.ConstGEP(bufInitializer.Type(), buf, []llvm.Value{zero, zero})
1234-
if ptr.Type() != elementTypes[0] {
1235-
return fmt.Errorf("%s: not a string", globalName)
1236-
}
1237-
length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false)
1238-
initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{
1239-
ptr,
1233+
length := llvm.ConstInt(uintptrType, uint64(len(value)), false)
1234+
initializer := llvm.ConstNamedStruct(stringType, []llvm.Value{
1235+
buf,
12401236
length,
12411237
})
12421238

1243-
// Set the initializer. No initializer should be set at this point.
1239+
// Create the string global.
1240+
global := llvm.AddGlobal(mod, stringType, globalName)
12441241
global.SetInitializer(initializer)
1242+
global.SetAlignment(targetData.PrefTypeAlignment(stringType))
12451243
}
12461244
}
1247-
return nil
1245+
1246+
return mod
12481247
}
12491248

12501249
// functionStackSizes keeps stack size information about a single function

0 commit comments

Comments
 (0)