Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/compat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ jobs:
- run: tinygo version
- run: make gen-device -j4
- run: go test -tags=llvm${{ env.LLVM }} -short -skip=TestErrors
- run: go test -tags=llvm${{ env.LLVM }} -run TestTrimPath -target=darwin/amd64
- run: make smoketest-quick XTENSA=0
120 changes: 100 additions & 20 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (
"encoding/json"
"errors"
"fmt"
"go/token"
"go/types"
"hash/crc32"
"io"
"maps"
"math/bits"
"os"
Expand All @@ -30,14 +32,14 @@ import (
"github.com/gofrs/flock"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader"
"github.com/tinygo-org/tinygo/stacksize"
"github.com/tinygo-org/tinygo/transform"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)

Expand Down Expand Up @@ -132,6 +134,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
},
"testing": {},
}
if config.TrimPath() {
globalValues["runtime"]["goroot"] = ""
}
if config.TestConfig.CompileTestBinary {
// The testing.testBinary is set to "1" when in a test.
// This is needed for testing.Testing() to work correctly.
Expand Down Expand Up @@ -213,6 +218,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
RelocationModel: config.RelocationModel(),
SizeLevel: sizeLevel,
TinyGoVersion: goenv.Version(),
TrimPath: config.TrimPath(),

Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(),
Expand Down Expand Up @@ -279,6 +285,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
result.PackagePathMap = make(map[string]string, len(lprogram.Packages))
for _, pkg := range lprogram.Sorted() {
result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
if config.TrimPath() {
result.PackagePathMap[filepath.FromSlash(pkg.RecordedDir())] = pkg.Pkg.Path()
}
}

// Strip default initializers for -X globals from the type info before
Expand Down Expand Up @@ -349,7 +358,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
}

job.result, err = createEmbedObjectFile(string(data), hexSum, name, pkg.OriginalDir(), tmpdir, compilerConfig)
job.result, err = createEmbedObjectFile(string(data), hexSum, name, pkg.RecordedDir(), tmpdir, compilerConfig)
return err
},
}
Expand Down Expand Up @@ -383,15 +392,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
CompilerBuildID: string(compilerBuildID),
LLVMVersion: llvm.Version,
Config: compilerConfig,
CFlags: pkg.CFlags,
CFlags: pkg.RecordedCFlags(),
FileHashes: make(map[string]string, len(pkg.FileHashes)),
EmbeddedFiles: make(map[string]string, len(allFiles)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
OptLevel: optLevel,
UndefinedGlobals: undefinedGlobals,
}
for filePath, hash := range pkg.FileHashes {
actionID.FileHashes[filePath] = hex.EncodeToString(hash)
actionID.FileHashes[pkg.RecordedPath(filePath)] = hex.EncodeToString(hash)
}
for name, files := range allFiles {
actionID.EmbeddedFiles[name] = files[0].Hash
Expand Down Expand Up @@ -450,9 +459,10 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// These headers could be compiled in parallel but the benefit
// is so small that it's probably not worth parallelizing.
// Packages are compiled independently anyway.
for _, cgoHeader := range pkg.CGoHeaders {
packageNameHash := sha256.Sum256([]byte(pkg.ImportPath))
for i, cgoHeader := range pkg.CGoHeaders {
// Store the header text in a temporary file.
f, err := os.CreateTemp(tmpdir, "cgosnippet-*.c")
f, err := os.Create(filepath.Join(tmpdir, fmt.Sprintf("cgosnippet-%x-%d.c", packageNameHash, i)))
if err != nil {
return err
}
Expand All @@ -464,6 +474,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe

// Compile the code (if there is any) to bitcode.
flags := append([]string{"-c", "-emit-llvm", "-o", f.Name() + ".bc", f.Name()}, pkg.CFlags...)
flags = append(flags, "-working-directory="+tmpdir)
if config.TrimPath() {
flags = append(flags,
"-ffile-prefix-map="+tmpdir+"="+config.CSourcePath(pkg.RecordedDir()),
)
}
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("clang", flags...)
}
Expand Down Expand Up @@ -633,7 +649,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
defer irbuilder.Dispose()
irbuilder.SetInsertPointAtEnd(block)
if config.Debug() && !config.Options.SkipDWARF {
addInitAllDebugInfo(mod, llvmInitFn, irbuilder, program)
pos := program.Fset.Position(program.ImportedPackage("runtime").Members["initAll"].Pos())
pos.Filename = lprogram.Packages["runtime"].RecordedPath(pos.Filename)
addInitAllDebugInfo(mod, llvmInitFn, irbuilder, pos, config.TrimPath())
}
ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
for _, pkg := range lprogram.Sorted() {
Expand Down Expand Up @@ -802,7 +820,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
job := &compileJob{
description: "compile extra file " + path,
run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(false), config.Options.PrintCommands)
var compileConfig *cFileCompileConfig
if config.TrimPath() {
compileConfig = &cFileCompileConfig{
recordedPath: config.CSourcePath(filepath.Join("github.com/tinygo-org/tinygo", path)),
}
}
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(false), compileConfig, config.Options.PrintCommands)
job.result = result
return err
},
Expand All @@ -819,7 +843,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
job := &compileJob{
description: "compile CGo file " + abspath,
run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, tmpdir, pkg.CFlags, config.Options.PrintCommands)
cflags := pkg.CFlags
var compileConfig *cFileCompileConfig
if config.TrimPath() {
compileConfig = &cFileCompileConfig{
recordedPath: config.CSourcePath(pkg.RecordedPath(abspath)),
}
}
result, err := compileAndCacheCFile(abspath, tmpdir, cflags, compileConfig, config.Options.PrintCommands)
job.result = result
return err
},
Expand Down Expand Up @@ -872,11 +903,18 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
description: "link",
dependencies: linkerDependencies,
run: func(job *compileJob) error {
for _, dependency := range job.dependencies {
for i, dependency := range job.dependencies {
if dependency.result == "" {
return errors.New("dependency without result: " + dependency.description)
}
ldflags = append(ldflags, dependency.result)
linkerInput := dependency.result
if config.TrimPath() && config.LinkerFlavor() == "darwin" {
linkerInput = filepath.Join(tmpdir, fmt.Sprintf("link-input-%d%s", i, filepath.Ext(linkerInput)))
if err := linkOrCopyFile(dependency.result, linkerInput); err != nil {
return err
Comment thread
jakebailey marked this conversation as resolved.
}
}
ldflags = append(ldflags, linkerInput)
}
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat
Expand All @@ -888,9 +926,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
case "darwin":
// Options for the ld64-compatible lld linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
ldflags = append(ldflags, "--lto-O"+strconv.Itoa(speedLevel))
// LLD 15 embeds cache paths in OSO symbols, unlike LLD 16+.
// Fixed in LLD 16 by https://reviews.llvm.org/D131624.
if !config.TrimPath() || llvmutil.Version() >= 16 {
ldflags = append(ldflags, "-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
}
if config.TrimPath() {
ldflags = append(ldflags, "-oso_prefix", tmpdir+string(filepath.Separator))
}
case "gnu":
// Options for the ELF linker.
ldflags = append(ldflags,
Expand All @@ -914,7 +958,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if config.Options.PrintCommands != nil {
config.Options.PrintCommands(config.Target.Linker, ldflags...)
}
err = link(config.Target.Linker, ldflags...)
var linkEnv []string
if config.TrimPath() && config.LinkerFlavor() == "darwin" {
linkEnv = append(linkEnv, "ZERO_AR_DATE=1")
}
err = link(config.Target.Linker, ldflags, linkEnv...)
if err != nil {
return err
}
Expand Down Expand Up @@ -1139,6 +1187,34 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return result, nil
}

func linkOrCopyFile(src, dst string) error {
if err := os.Link(src, dst); err == nil {
return nil
}

source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()

destination, err := os.Create(dst)
if err != nil {
return err
}
_, copyErr := io.Copy(destination, source)
closeErr := destination.Close()
if copyErr != nil {
os.Remove(dst)
return copyErr
}
if closeErr != nil {
os.Remove(dst)
return closeErr
}
return nil
}

// createEmbedObjectFile creates a new object file with the given contents, for
// the embed package.
func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, compilerConfig *compiler.Config) (string, error) {
Expand Down Expand Up @@ -1228,7 +1304,8 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c
return "", err
}
defer machine.Dispose()
outfile, err := os.CreateTemp(tmpdir, "embed-"+hexSum+"-*.o")
sourcePathHash := sha256.Sum256([]byte(filepath.ToSlash(filepath.Join(sourceDir, sourceFile))))
outfile, err := os.Create(filepath.Join(tmpdir, "embed-"+hexSum+"-"+hex.EncodeToString(sourcePathHash[:8])+".o"))
if err != nil {
return "", err
}
Expand Down Expand Up @@ -1281,15 +1358,18 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {

// addInitAllDebugInfo gives runtime.initAll a subprogram so that code emitted
// into it by interp or the inliner keeps its line information.
func addInitAllDebugInfo(mod llvm.Module, fn llvm.Value, irbuilder llvm.Builder, program *ssa.Program) {
pos := program.Fset.Position(program.ImportedPackage("runtime").Members["initAll"].Pos())
func addInitAllDebugInfo(mod llvm.Module, fn llvm.Value, irbuilder llvm.Builder, pos token.Position, trimPath bool) {
dir, file := filepath.Split(pos.Filename)
compileDir, compileFile := filepath.Clean(dir), file
if trimPath {
compileDir, compileFile = "", pos.Filename
}
dibuilder := llvm.NewDIBuilder(mod)
defer dibuilder.Destroy()
dibuilder.CreateCompileUnit(llvm.DICompileUnit{
Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?)
File: file,
Dir: filepath.Clean(dir),
File: compileFile,
Dir: compileDir,
Producer: "TinyGo",
Optimized: true,
})
Expand Down
46 changes: 36 additions & 10 deletions builder/cc.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"io"
"io/fs"
"os"
"path"
"path/filepath"
"sort"
"strings"
Expand All @@ -21,6 +22,10 @@ import (
"tinygo.org/x/go-llvm"
)

type cFileCompileConfig struct {
recordedPath string
}

// compileAndCacheCFile compiles a C or assembly file using a build cache.
// Compiling the same file again (if nothing changed, including included header
// files) the output is loaded from the build cache instead.
Expand Down Expand Up @@ -56,7 +61,7 @@ import (
// depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it
// could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) {
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, compileConfig *cFileCompileConfig, printCommands func(string, ...string)) (string, error) {
// Hash input file.
fileHash, err := hashFile(abspath)
if err != nil {
Expand All @@ -68,16 +73,22 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
defer unlock()

// Create cache key for the dependencies file.
recordedPath := ""
if compileConfig != nil {
recordedPath = compileConfig.recordedPath
}
buf, err := json.Marshal(struct {
Path string
Hash string
Flags []string
LLVMVersion string
Path string
Hash string
Flags []string
RecordedPath string
LLVMVersion string
}{
Path: abspath,
Hash: fileHash,
Flags: cflags,
LLVMVersion: llvm.Version,
Path: abspath,
Hash: fileHash,
Flags: cflags,
RecordedPath: recordedPath,
LLVMVersion: llvm.Version,
})
if err != nil {
panic(err) // shouldn't happen
Expand Down Expand Up @@ -124,7 +135,17 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
depTmpFile.Close()
flags := append([]string{}, cflags...) // copy cflags
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name(), "-flto=thin") // autogenerate dependencies
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
// Match cmd/go/internal/work/exec.go's ccompile directory in both modes.
workingDir := filepath.Dir(abspath)
flags = append(flags, "-working-directory="+workingDir)
if compileConfig != nil {
seed := sha512.Sum512_224([]byte(compileConfig.recordedPath))
flags = append(flags,
"-fdebug-compilation-dir="+path.Dir(compileConfig.recordedPath),
"-frandom-seed="+hex.EncodeToString(seed[:]),
)
}
flags = append(flags, "-c", "-o", objTmpFile.Name(), filepath.Base(abspath))
if strings.ToLower(filepath.Ext(abspath)) == ".s" {
// If this is an assembly file (.s or .S, lowercase or uppercase), then
// we'll need to add -Qunused-arguments because many parameters are
Expand All @@ -145,6 +166,11 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
if err != nil {
return "", err
}
for i, dependencyPath := range dependencyPaths {
if !filepath.IsAbs(dependencyPath) {
dependencyPaths[i] = filepath.Join(workingDir, dependencyPath)
}
}
dependencyPaths = append(dependencyPaths, abspath) // necessary for .s files
dependencySet := make(map[string]struct{}, len(dependencyPaths))
var dependencySlice []string
Expand Down
2 changes: 1 addition & 1 deletion builder/darwin-libsystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJ
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("ld.lld", flags...)
}
return link("ld.lld", flags...)
return link("ld.lld", flags)
},
}
}
Loading
Loading