mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 14:48:40 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2de0635140 | |||
| 9b91cbb841 | |||
| fa6bc6445c |
+92
-29
@@ -169,12 +169,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
SizeLevel: sizeLevel,
|
||||
TinyGoVersion: goenv.Version,
|
||||
|
||||
Scheduler: config.Scheduler(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
DefaultStackSize: config.StackSize(),
|
||||
NeedsStackObjects: config.NeedsStackObjects(),
|
||||
Debug: true,
|
||||
LTO: config.LTO() != "legacy",
|
||||
}
|
||||
|
||||
// Load the target machine, which is the LLVM object that contains all
|
||||
@@ -305,7 +307,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
actionID := packageAction{
|
||||
ImportPath: pkg.ImportPath,
|
||||
CompilerBuildID: string(compilerBuildID),
|
||||
TinyGoVersion: goenv.Version,
|
||||
LLVMVersion: llvm.Version,
|
||||
Config: compilerConfig,
|
||||
CFlags: pkg.CFlags,
|
||||
@@ -452,18 +453,24 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
// Work around a problem on Windows.
|
||||
// For some reason, WriteBitcodeToFile causes TinyGo to
|
||||
// exit with the following message:
|
||||
// LLVM ERROR: IO failure on output stream: Bad file descriptor
|
||||
buf := llvm.WriteBitcodeToMemoryBuffer(mod)
|
||||
if compilerConfig.LTO {
|
||||
buf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
|
||||
defer buf.Dispose()
|
||||
_, err = f.Write(buf.Bytes())
|
||||
} else {
|
||||
// Otherwise, write bitcode directly to the file (probably
|
||||
// faster).
|
||||
err = llvm.WriteBitcodeToFile(mod, f)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Work around a problem on Windows.
|
||||
// For some reason, WriteBitcodeToFile causes TinyGo to
|
||||
// exit with the following message:
|
||||
// LLVM ERROR: IO failure on output stream: Bad file descriptor
|
||||
buf := llvm.WriteBitcodeToMemoryBuffer(mod)
|
||||
defer buf.Dispose()
|
||||
_, err = f.Write(buf.Bytes())
|
||||
} else {
|
||||
// Otherwise, write bitcode directly to the file (probably
|
||||
// faster).
|
||||
err = llvm.WriteBitcodeToFile(mod, f)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
// WriteBitcodeToFile doesn't produce a useful error on its
|
||||
@@ -511,24 +518,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
|
||||
// Create runtime.initAll function that calls the runtime
|
||||
// initializer of each package.
|
||||
llvmInitFn := mod.NamedFunction("runtime.initAll")
|
||||
llvmInitFn.SetLinkage(llvm.InternalLinkage)
|
||||
llvmInitFn.SetUnnamedAddr(true)
|
||||
transform.AddStandardAttributes(llvmInitFn, config)
|
||||
llvmInitFn.Param(0).SetName("context")
|
||||
block := mod.Context().AddBasicBlock(llvmInitFn, "entry")
|
||||
irbuilder := mod.Context().NewBuilder()
|
||||
defer irbuilder.Dispose()
|
||||
irbuilder.SetInsertPointAtEnd(block)
|
||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||
for _, pkg := range lprogram.Sorted() {
|
||||
pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init")
|
||||
if pkgInit.IsNil() {
|
||||
panic("init not found for " + pkg.Pkg.Path())
|
||||
}
|
||||
irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "")
|
||||
initAllModule := createInitAll(ctx, config, compilerConfig, lprogram.Sorted())
|
||||
err := llvm.LinkModules(mod, initAllModule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to link module: %w", err)
|
||||
}
|
||||
irbuilder.CreateRetVoid()
|
||||
|
||||
// After linking, functions should (as far as possible) be set to
|
||||
// private linkage or internal linkage. The compiler package marks
|
||||
@@ -561,7 +555,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
|
||||
// Run all optimization passes, which are much more effective now
|
||||
// that the optimizer can see the whole program at once.
|
||||
err := optimizeProgram(mod, config)
|
||||
err = optimizeProgram(mod, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -622,8 +616,38 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
},
|
||||
}
|
||||
|
||||
// Add job to create the runtime.initAll function in a new module.
|
||||
initAllJob := &compileJob{
|
||||
description: "create runtime.initAll",
|
||||
result: filepath.Join(tmpdir, "runtime-initAll.bc"),
|
||||
run: func(job *compileJob) (err error) {
|
||||
// Create module with runtime.initAll.
|
||||
ctx := llvm.NewContext()
|
||||
defer ctx.Dispose()
|
||||
initAllMod := createInitAll(ctx, config, compilerConfig, lprogram.Sorted())
|
||||
defer initAllMod.Dispose()
|
||||
|
||||
// Write module to bitcode file.
|
||||
llvmBuf := llvm.WriteThinLTOBitcodeToMemoryBuffer(initAllMod)
|
||||
defer llvmBuf.Dispose()
|
||||
return os.WriteFile(job.result, llvmBuf.Bytes(), 0666)
|
||||
},
|
||||
}
|
||||
|
||||
// Prepare link command.
|
||||
linkerDependencies := []*compileJob{outputObjectFileJob}
|
||||
var linkerDependencies []*compileJob
|
||||
switch config.LTO() {
|
||||
case "legacy":
|
||||
// Link all Go bitcode files together into a single large module and
|
||||
// then do a ThinLTO link with the resulting large module + extra C
|
||||
// files (from CGo etc).
|
||||
linkerDependencies = append(linkerDependencies, outputObjectFileJob)
|
||||
case "thin":
|
||||
// Do a real thin link, with each Go package in a separate translation
|
||||
// unit. This is faster than merging them into one big LTO module.
|
||||
linkerDependencies = append(linkerDependencies, packageJobs...)
|
||||
linkerDependencies = append(linkerDependencies, initAllJob)
|
||||
}
|
||||
result.Executable = filepath.Join(tmpdir, "main")
|
||||
if config.GOOS() == "windows" {
|
||||
result.Executable += ".exe"
|
||||
@@ -1032,6 +1056,45 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c
|
||||
return outfile.Name(), outfile.Close()
|
||||
}
|
||||
|
||||
// Create a module with the runtime.initAll function that calls all package
|
||||
// initializers in initialization order.
|
||||
func createInitAll(ctx llvm.Context, config *compileopts.Config, compilerConfig *compiler.Config, pkgs []*loader.Package) llvm.Module {
|
||||
// Create LLVM module.
|
||||
mod := ctx.NewModule("runtime-initAll")
|
||||
|
||||
// Add datalayout string.
|
||||
machine, err := compiler.NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
panic(err) // extremely unlikely to fail here (NewTargetMachine is called many times before)
|
||||
}
|
||||
defer machine.Dispose()
|
||||
targetData := machine.CreateTargetData()
|
||||
defer targetData.Dispose()
|
||||
mod.SetTarget(config.Triple())
|
||||
mod.SetDataLayout(targetData.String())
|
||||
|
||||
// Create empty runtime.initAll function.
|
||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{i8ptrType}, false)
|
||||
fn := llvm.AddFunction(mod, "runtime.initAll", fnType)
|
||||
fn.SetUnnamedAddr(true)
|
||||
transform.AddStandardAttributes(fn, config)
|
||||
|
||||
// Add calls to package initializers.
|
||||
fn.Param(0).SetName("context")
|
||||
block := mod.Context().AddBasicBlock(fn, "entry")
|
||||
irbuilder := mod.Context().NewBuilder()
|
||||
defer irbuilder.Dispose()
|
||||
irbuilder.SetInsertPointAtEnd(block)
|
||||
for _, pkg := range pkgs {
|
||||
pkgInit := llvm.AddFunction(mod, pkg.Pkg.Path()+".init", fnType)
|
||||
irbuilder.CreateCall(fnType, pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "")
|
||||
}
|
||||
irbuilder.CreateRetVoid()
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
// optimizeProgram runs a series of optimizations and transformations that are
|
||||
// needed to convert a program to its final form. Some transformations are not
|
||||
// optional and must be run as the compiler expects them to run.
|
||||
|
||||
@@ -165,6 +165,14 @@ func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
|
||||
}
|
||||
}
|
||||
|
||||
// LTO returns one of the possible LTO configurations: legacy or thin.
|
||||
func (c *Config) LTO() string {
|
||||
if c.Options.LTO != "" {
|
||||
return c.Options.LTO
|
||||
}
|
||||
return "legacy"
|
||||
}
|
||||
|
||||
// PanicStrategy returns the panic strategy selected for this target. Valid
|
||||
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
||||
// instruction).
|
||||
|
||||
@@ -14,6 +14,7 @@ var (
|
||||
validPrintSizeOptions = []string{"none", "short", "full"}
|
||||
validPanicStrategyOptions = []string{"print", "trap"}
|
||||
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
|
||||
validLTOOptions = []string{"legacy", "thin"}
|
||||
)
|
||||
|
||||
// Options contains extra options to give to the compiler. These options are
|
||||
@@ -25,6 +26,7 @@ type Options struct {
|
||||
GOARM string // environment variable (only used with GOARCH=arm)
|
||||
Target string
|
||||
Opt string
|
||||
LTO string
|
||||
GC string
|
||||
PanicStrategy string
|
||||
Scheduler string
|
||||
@@ -107,6 +109,12 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
}
|
||||
|
||||
if o.LTO != "" {
|
||||
if !isInArray(validLTOOptions, o.LTO) {
|
||||
return fmt.Errorf("invalid -lto=%s: valid values are %s", o.LTO, strings.Join(validLTOOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -34,7 +34,9 @@ var stdlibAliases = map[string]string{
|
||||
// createAlias implements the function (in the builder) as a call to the alias
|
||||
// function.
|
||||
func (b *builder) createAlias(alias llvm.Value) {
|
||||
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
|
||||
if !b.LTO {
|
||||
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
|
||||
}
|
||||
b.llvmFn.SetUnnamedAddr(true)
|
||||
|
||||
if b.Debug {
|
||||
|
||||
+24
-5
@@ -47,6 +47,7 @@ type Config struct {
|
||||
CodeModel string
|
||||
RelocationModel string
|
||||
SizeLevel int
|
||||
TinyGoVersion string // for llvm.ident
|
||||
|
||||
// Various compiler options that determine how code is generated.
|
||||
Scheduler string
|
||||
@@ -54,6 +55,7 @@ type Config struct {
|
||||
DefaultStackSize uint64
|
||||
NeedsStackObjects bool
|
||||
Debug bool // Whether to emit debug information in the LLVM module.
|
||||
LTO bool // non-legacy LTO (meaning: package bitcode is merged by the linker)
|
||||
}
|
||||
|
||||
// compilerContext contains function-independent data that should still be
|
||||
@@ -321,6 +323,14 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(),
|
||||
}),
|
||||
)
|
||||
if c.TinyGoVersion != "" {
|
||||
// It is necessary to set llvm.ident, otherwise debugging on MacOS
|
||||
// won't work.
|
||||
c.mod.AddNamedMetadataOperand("llvm.ident",
|
||||
c.ctx.MDNode(([]llvm.Metadata{
|
||||
c.ctx.MDString("TinyGo version " + c.TinyGoVersion),
|
||||
})))
|
||||
}
|
||||
c.dibuilder.Finalize()
|
||||
c.dibuilder.Destroy()
|
||||
}
|
||||
@@ -888,7 +898,9 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
|
||||
c.createEmbedGlobal(member, global, files)
|
||||
} else if !info.extern {
|
||||
global.SetInitializer(llvm.ConstNull(global.GlobalValueType()))
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
if !c.LTO {
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
}
|
||||
if info.section != "" {
|
||||
global.SetSection(info.section)
|
||||
}
|
||||
@@ -935,7 +947,9 @@ func (c *compilerContext) createEmbedGlobal(member *ssa.Global, global llvm.Valu
|
||||
}
|
||||
strObj := c.getEmbedFileString(files[0])
|
||||
global.SetInitializer(strObj)
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
if !c.LTO {
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
}
|
||||
|
||||
case *types.Slice:
|
||||
if typ.Elem().Underlying().(*types.Basic).Kind() != types.Byte {
|
||||
@@ -959,7 +973,9 @@ func (c *compilerContext) createEmbedGlobal(member *ssa.Global, global llvm.Valu
|
||||
sliceLen := llvm.ConstInt(c.uintptrType, file.Size, false)
|
||||
sliceObj := c.ctx.ConstStruct([]llvm.Value{slicePtr, sliceLen, sliceLen}, false)
|
||||
global.SetInitializer(sliceObj)
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
if !c.LTO {
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
}
|
||||
|
||||
case *types.Struct:
|
||||
// Assume this is an embed.FS struct:
|
||||
@@ -1042,7 +1058,9 @@ func (c *compilerContext) createEmbedGlobal(member *ssa.Global, global llvm.Valu
|
||||
globalInitializer := llvm.ConstNull(c.getLLVMType(member.Type().(*types.Pointer).Elem()))
|
||||
globalInitializer = c.builder.CreateInsertValue(globalInitializer, sliceGlobal, 0, "")
|
||||
global.SetInitializer(globalInitializer)
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
if !c.LTO {
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
}
|
||||
global.SetAlignment(c.targetData.ABITypeAlignment(globalInitializer.Type()))
|
||||
}
|
||||
}
|
||||
@@ -1089,7 +1107,8 @@ func (b *builder) createFunctionStart(intrinsic bool) {
|
||||
// assertion error in llvm-project/llvm/include/llvm/IR/GlobalValue.h:236
|
||||
// is thrown.
|
||||
if b.llvmFn.Linkage() != llvm.InternalLinkage &&
|
||||
b.llvmFn.Linkage() != llvm.PrivateLinkage {
|
||||
b.llvmFn.Linkage() != llvm.PrivateLinkage &&
|
||||
!b.LTO {
|
||||
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
|
||||
}
|
||||
b.llvmFn.SetUnnamedAddr(true)
|
||||
|
||||
+15
-10
@@ -490,17 +490,22 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "")
|
||||
|
||||
} else {
|
||||
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
|
||||
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
||||
if assertedTypeCodeGlobal.IsNil() {
|
||||
// Create a new typecode global.
|
||||
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
|
||||
assertedTypeCodeGlobal.SetGlobalConstant(true)
|
||||
if b.LTO {
|
||||
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
|
||||
commaOk = b.CreateICmp(llvm.IntEQ, actualTypeNum, assertedTypeCodeGlobal, "commaok")
|
||||
} else {
|
||||
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
|
||||
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
||||
if assertedTypeCodeGlobal.IsNil() {
|
||||
// Create a new typecode global.
|
||||
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
|
||||
assertedTypeCodeGlobal.SetGlobalConstant(true)
|
||||
}
|
||||
// Type assert on concrete type.
|
||||
// Call runtime.typeAssert, which will be lowered to a simple icmp
|
||||
// or const false in the interface lowering pass.
|
||||
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
||||
}
|
||||
// Type assert on concrete type.
|
||||
// Call runtime.typeAssert, which will be lowered to a simple icmp or
|
||||
// const false in the interface lowering pass.
|
||||
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
||||
}
|
||||
|
||||
// Add 2 new basic blocks (that should get optimized away): one for the
|
||||
|
||||
@@ -45,7 +45,9 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
||||
globalLLVMType := b.getLLVMType(globalType)
|
||||
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
||||
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
if !b.LTO {
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
}
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
initializer := llvm.ConstNull(globalLLVMType)
|
||||
|
||||
@@ -1357,6 +1357,7 @@ func main() {
|
||||
command := os.Args[1]
|
||||
|
||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||
lto := flag.String("lto", "legacy", "LTO mode: legacy or thin")
|
||||
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
||||
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
|
||||
scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, asyncify)")
|
||||
@@ -1459,6 +1460,7 @@ func main() {
|
||||
Target: *target,
|
||||
StackSize: stackSize,
|
||||
Opt: *opt,
|
||||
LTO: *lto,
|
||||
GC: *gc,
|
||||
PanicStrategy: *panicStrategy,
|
||||
Scheduler: *scheduler,
|
||||
|
||||
@@ -105,6 +105,13 @@ func TestBuild(t *testing.T) {
|
||||
runTestWithConfig("print.go", t, opts, nil, nil)
|
||||
})
|
||||
|
||||
t.Run("lto=thin", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := optionsFromTarget("", sema)
|
||||
opts.LTO = "thin"
|
||||
runTestWithConfig("init.go", t, opts, nil, nil)
|
||||
})
|
||||
|
||||
t.Run("ldflags", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
opts := optionsFromTarget("", sema)
|
||||
|
||||
Reference in New Issue
Block a user