diff --git a/builder/build.go b/builder/build.go index e5977770..0361dd64 100644 --- a/builder/build.go +++ b/builder/build.go @@ -4,14 +4,18 @@ package builder import ( + "crypto/sha512" "debug/elf" "encoding/binary" + "encoding/hex" + "encoding/json" "errors" "fmt" "go/types" "io/ioutil" "os" "path/filepath" + "runtime" "sort" "strconv" "strings" @@ -38,6 +42,26 @@ type BuildResult struct { MainDir string } +// packageAction is the struct that is serialized to JSON and hashed, to work as +// a cache key of compiled packages. It should contain all the information that +// goes into a compiled package to avoid using stale data. +// +// Right now it's still important to include a hash of every import, because a +// dependency might have a public constant that this package uses and thus this +// package will need to be recompiled if that constant changes. In the future, +// the type data should be serialized to disk which can then be used as cache +// key, avoiding the need for recompiling all dependencies when only the +// implementation of an imported package changes. +type packageAction struct { + ImportPath string + CompilerVersion int // compiler.Version + LLVMVersion string + Config *compiler.Config + CFlags []string + FileHashes map[string]string // hash of every file that's part of the package + Imports map[string]string // map from imported package to action ID hash +} + // Build performs a single package to executable Go build. It takes in a package // name, an output path, and set of compile options and from that it manages the // whole compilation process. @@ -45,6 +69,13 @@ type BuildResult struct { // The error value may be of type *MultiError. Callers will likely want to check // for this case and print such errors individually. func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error { + // Create a temporary directory for intermediary files. + dir, err := ioutil.TempDir("", "tinygo") + if err != nil { + return err + } + defer os.RemoveAll(dir) + compilerConfig := &compiler.Config{ Triple: config.Triple(), CPU: config.CPU(), @@ -87,23 +118,200 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil // Makefile target. var jobs []*compileJob - // Add job to compile and optimize all Go files at once. - // TODO: parallelize this. + // Create the *ssa.Program. This does not yet build the entire SSA of the + // program so it's pretty fast and doesn't need to be parallelized. + program := lprogram.LoadSSA() + + // Add jobs to compile each package. + // Packages that have a cache hit will not be compiled again. + var packageJobs []*compileJob + packageBitcodePaths := make(map[string]string) + packageActionIDs := make(map[string]string) + for _, pkg := range lprogram.Sorted() { + pkg := pkg // necessary to avoid a race condition + + // Create a cache key: a hash from the action ID below that contains all + // the parameters for the build. + actionID := packageAction{ + ImportPath: pkg.ImportPath, + CompilerVersion: compiler.Version, + LLVMVersion: llvm.Version, + Config: compilerConfig, + CFlags: pkg.CFlags, + FileHashes: make(map[string]string, len(pkg.FileHashes)), + Imports: make(map[string]string, len(pkg.Pkg.Imports())), + } + for filePath, hash := range pkg.FileHashes { + actionID.FileHashes[filePath] = hex.EncodeToString(hash) + } + for _, imported := range pkg.Pkg.Imports() { + hash, ok := packageActionIDs[imported.Path()] + if !ok { + return fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path()) + } + actionID.Imports[imported.Path()] = hash + } + buf, err := json.Marshal(actionID) + if err != nil { + panic(err) // shouldn't happen + } + hash := sha512.Sum512_224(buf) + packageActionIDs[pkg.ImportPath] = hex.EncodeToString(hash[:]) + + // Determine the path of the bitcode file (which is a serialized version + // of a LLVM module). + cacheDir := goenv.Get("GOCACHE") + if cacheDir == "off" { + // Use temporary build directory instead, effectively disabling the + // build cache. + cacheDir = dir + } + bitcodePath := filepath.Join(cacheDir, "pkg-"+hex.EncodeToString(hash[:])+".bc") + packageBitcodePaths[pkg.ImportPath] = bitcodePath + + // Check whether this package has been compiled before, and if so don't + // compile it again. + if _, err := os.Stat(bitcodePath); err == nil { + // Already cached, don't recreate this package. + continue + } + + // The package has not yet been compiled, so create a job to do so. + job := &compileJob{ + description: "compile package " + pkg.ImportPath, + run: func() error { + // Compile AST to IR. The compiler.CompilePackage function will + // build the SSA as needed. + mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA()) + if errs != nil { + return newMultiError(errs) + } + if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { + return errors.New("verification error after compiling package " + pkg.ImportPath) + } + + // Serialize the LLVM module as a bitcode file. + // Write to a temporary path that is renamed to the destination + // file to avoid race conditions with other TinyGo invocatiosn + // that might also be compiling this package at the same time. + f, err := ioutil.TempFile(filepath.Dir(bitcodePath), filepath.Base(bitcodePath)) + 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) + 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 + // own, so create a somewhat useful error message here. + return fmt.Errorf("failed to write bitcode for package %s to file %s", pkg.ImportPath, bitcodePath) + } + err = f.Close() + if err != nil { + return err + } + return os.Rename(f.Name(), bitcodePath) + }, + } + jobs = append(jobs, job) + packageJobs = append(packageJobs, job) + } + + // Add job that links and optimizes all packages together. var mod llvm.Module var stackSizeLoads []string programJob := &compileJob{ - description: "compile Go files", - run: func() (err error) { - mod, err = compileWholeProgram(pkgName, config, compilerConfig, lprogram, machine) - if err != nil { - return + description: "link+optimize packages (LTO)", + dependencies: packageJobs, + run: func() error { + // Load and link all the bitcode files. This does not yet optimize + // anything, it only links the bitcode files together. + ctx := llvm.NewContext() + mod = ctx.NewModule("") + for _, pkg := range lprogram.Sorted() { + pkgMod, err := ctx.ParseBitcodeFile(packageBitcodePaths[pkg.ImportPath]) + if err != nil { + return fmt.Errorf("failed to load bitcode file: %w", err) + } + err = llvm.LinkModules(mod, pkgMod) + if err != nil { + return fmt.Errorf("failed to link module: %w", err) + } } + + // Create runtime.initAll function that calls the runtime + // initializer of each package. + llvmInitFn := mod.NamedFunction("runtime.initAll") + llvmInitFn.SetLinkage(llvm.InternalLinkage) + llvmInitFn.SetUnnamedAddr(true) + llvmInitFn.Param(0).SetName("context") + llvmInitFn.Param(1).SetName("parentHandle") + 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, []llvm.Value{llvm.Undef(i8ptrType), llvm.Undef(i8ptrType)}, "") + } + irbuilder.CreateRetVoid() + + // After linking, functions should (as far as possible) be set to + // private linkage or internal linkage. The compiler package marks + // non-exported functions by setting the visibility to hidden or + // (for thunks) to linkonce_odr linkage. Change the linkage here to + // internal to benefit much more from interprocedural optimizations. + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + if fn.Visibility() == llvm.HiddenVisibility { + fn.SetVisibility(llvm.DefaultVisibility) + fn.SetLinkage(llvm.InternalLinkage) + } else if fn.Linkage() == llvm.LinkOnceODRLinkage { + fn.SetLinkage(llvm.InternalLinkage) + } + } + + // Do the same for globals. + for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { + if global.Visibility() == llvm.HiddenVisibility { + global.SetVisibility(llvm.DefaultVisibility) + global.SetLinkage(llvm.InternalLinkage) + } else if global.Linkage() == llvm.LinkOnceODRLinkage { + global.SetLinkage(llvm.InternalLinkage) + } + } + + if config.Options.PrintIR { + fmt.Println("; Generated LLVM IR:") + fmt.Println(mod.String()) + } + + // Run all optimization passes, which are much more effective now + // that the optimizer can see the whole program at once. + err := optimizeProgram(mod, config) + if err != nil { + return err + } + // Make sure stack sizes are loaded from a separate section so they can be // modified after linking. if config.AutomaticStackSize() { stackSizeLoads = transform.CreateStackSizeLoads(mod, config) } - return + return nil }, } jobs = append(jobs, programJob) @@ -140,13 +348,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil // First add all jobs necessary to build this object file, then afterwards // run all jobs in parallel as far as possible. - // Create a temporary directory for intermediary files. - dir, err := ioutil.TempDir("", "tinygo") - if err != nil { - return err - } - defer os.RemoveAll(dir) - // Add job to write the output object file. objfile := filepath.Join(dir, "main.o") outputObjectFileJob := &compileJob{ @@ -366,29 +567,16 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil }) } -// compileWholeProgram compiles the entire *loader.Program to a LLVM module and -// applies most necessary optimizations and transformations. -func compileWholeProgram(pkgName string, config *compileopts.Config, compilerConfig *compiler.Config, lprogram *loader.Program, machine llvm.TargetMachine) (llvm.Module, error) { - // Compile AST to IR. - mod, errs := compiler.CompileProgram(lprogram, machine, compilerConfig, config.DumpSSA()) - if errs != nil { - return mod, newMultiError(errs) - } - - if config.Options.PrintIR { - fmt.Println("; Generated LLVM IR:") - fmt.Println(mod.String()) - } - if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { - return mod, errors.New("verification error after IR construction") - } - +// 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. +func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { err := interp.Run(mod, config.DumpSSA()) if err != nil { - return mod, err + return err } if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { - return mod, errors.New("verification error after interpreting runtime.initAll") + return errors.New("verification error after interpreting runtime.initAll") } if config.GOOS() != "darwin" { @@ -403,13 +591,13 @@ func compileWholeProgram(pkgName string, config *compileopts.Config, compilerCon if config.WasmAbi() == "js" { err := transform.ExternalInt64AsPtr(mod) if err != nil { - return mod, err + return err } } // Optimization levels here are roughly the same as Clang, but probably not // exactly. - errs = nil + var errs []error switch config.Options.Opt { case "none", "0": errs = transform.Optimize(mod, config, 0, 0, 0) // -O0 @@ -422,13 +610,13 @@ func compileWholeProgram(pkgName string, config *compileopts.Config, compilerCon case "z": errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default default: - errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)} + return errors.New("unknown optimization level: -opt=" + config.Options.Opt) } if len(errs) > 0 { - return mod, newMultiError(errs) + return newMultiError(errs) } if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { - return mod, errors.New("verification failure after LLVM optimization passes") + return errors.New("verification failure after LLVM optimization passes") } // LLVM 11 by default tries to emit tail calls (even with the target feature @@ -442,7 +630,7 @@ func compileWholeProgram(pkgName string, config *compileopts.Config, compilerCon transform.DisableTailCalls(mod) } - return mod, nil + return nil } // functionStackSizes keeps stack size information about a single function diff --git a/cgo/cgo.go b/cgo/cgo.go index 73d26e1d..c496cfef 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -42,6 +42,7 @@ type cgoPackage struct { enums map[string]enumInfo anonStructNum int ldflags []string + visitedFiles map[string][]byte } // constantInfo stores some information about a CGo constant found by libclang @@ -156,9 +157,10 @@ typedef unsigned long long _Cgo_ulonglong; // Process extracts `import "C"` statements from the AST, parses the comment // with libclang, and modifies the AST to use this information. It returns a // newly created *ast.File that should be added to the list of to-be-parsed -// files. If there is one or more error, it returns these in the []error slice -// but still modifies the AST. -func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) { +// files, the LDFLAGS for this package, and a map of file hashes of the accessed +// C header files. If there is one or more error, it returns these in the +// []error slice but still modifies the AST. +func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, map[string][]byte, []error) { p := &cgoPackage{ dir: dir, fset: fset, @@ -170,6 +172,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string typedefs: map[string]*typedefInfo{}, elaboratedTypes: map[string]*elaboratedTypeInfo{}, enums: map[string]enumInfo{}, + visitedFiles: map[string][]byte{}, } // Disable _FORTIFY_SOURCE as it causes problems on macOS. @@ -185,7 +188,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string // Find the absolute path for this package. packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name()) if err != nil { - return nil, nil, []error{ + return nil, nil, nil, []error{ scanner.Error{ Pos: fset.Position(files[0].Pos()), Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error @@ -427,7 +430,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string // Print the newly generated in-memory AST, for debugging. //ast.Print(fset, p.generated) - return p.generated, p.ldflags, p.errors + return p.generated, p.ldflags, p.visitedFiles, p.errors } // makePathsAbsolute converts some common path compiler flags (-I, -L) from diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go index e0fbc633..33a49b96 100644 --- a/cgo/cgo_test.go +++ b/cgo/cgo_test.go @@ -65,7 +65,7 @@ func TestCGo(t *testing.T) { } // Process the AST with CGo. - cgoAST, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags) + cgoAST, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags) // Check the AST for type errors. var typecheckErrors []error diff --git a/cgo/libclang.go b/cgo/libclang.go index f1ba3baa..8e9af8a0 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -4,6 +4,7 @@ package cgo // modification. It does not touch the AST itself. import ( + "crypto/sha512" "fmt" "go/ast" "go/scanner" @@ -56,6 +57,7 @@ unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c); int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); +void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data); */ import "C" @@ -114,6 +116,7 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename } defer C.clang_disposeTranslationUnit(unit) + // Report parser and type errors. if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 { addDiagnostic := func(diagnostic C.CXDiagnostic) { spelling := getString(C.clang_getDiagnosticSpelling(diagnostic)) @@ -134,10 +137,36 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename } } + // Extract information required by CGo. ref := storedRefs.Put(p) defer storedRefs.Remove(ref) cursor := C.tinygo_clang_getTranslationUnitCursor(unit) C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref)) + + // Determine files read during CGo processing, for caching. + inclusionCallback := func(includedFile C.CXFile) { + // Get full file path. + path := getString(C.clang_getFileName(includedFile)) + + // Get contents of file (that should be in-memory). + size := C.size_t(0) + rawData := C.clang_getFileContents(unit, includedFile, &size) + if rawData == nil { + // Sanity check. This should (hopefully) never trigger. + panic("libclang: file contents was not loaded") + } + data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size] + + // Hash the contents if it isn't hashed yet. + if _, ok := p.visitedFiles[path]; !ok { + // already stored + sum := sha512.Sum512_224(data) + p.visitedFiles[path] = sum[:] + } + } + inclusionCallbackRef := storedRefs.Put(inclusionCallback) + defer storedRefs.Remove(inclusionCallbackRef) + C.clang_getInclusions(unit, C.CXInclusionVisitor(C.tinygo_clang_inclusion_visitor), C.CXClientData(inclusionCallbackRef)) } //export tinygo_clang_globals_visitor @@ -772,3 +801,9 @@ func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientDat } return C.CXChildVisit_Continue } + +//export tinygo_clang_inclusion_visitor +func tinygo_clang_inclusion_visitor(includedFile C.CXFile, inclusionStack *C.CXSourceLocation, includeLen C.unsigned, clientData C.CXClientData) { + callback := storedRefs.Get(unsafe.Pointer(clientData)).(func(C.CXFile)) + callback(includedFile) +} diff --git a/compiler/compiler.go b/compiler/compiler.go index e25cc673..abd7be64 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -20,6 +20,11 @@ import ( "tinygo.org/x/go-llvm" ) +// Version of the compiler pacakge. Must be incremented each time the compiler +// package changes in a way that affects the generated LLVM module. +// This version is independent of the TinyGo version number. +const Version = 1 + func init() { llvm.InitializeAllTargets() llvm.InitializeAllTargetMCs() @@ -242,15 +247,14 @@ func Sizes(machine llvm.TargetMachine) types.Sizes { } } -// CompileProgram compiles the given package path or .go file path. Return an -// error when this fails (in any stage). If successful it returns the LLVM -// module. If not, one or more errors will be returned. -func CompileProgram(lprogram *loader.Program, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) { - c := newCompilerContext("", machine, config, dumpSSA) +// CompilePackage compiles a single package to a LLVM module. +func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) { + c := newCompilerContext(moduleName, machine, config, dumpSSA) + c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg + c.program = ssaPkg.Prog - c.program = lprogram.LoadSSA() - c.program.Build() - c.runtimePkg = c.program.ImportedPackage("runtime").Pkg + // Convert AST to SSA. + ssaPkg.Build() // Initialize debug information. if c.Debug { @@ -263,43 +267,17 @@ func CompileProgram(lprogram *loader.Program, machine llvm.TargetMachine, config }) } - for _, pkg := range lprogram.Sorted() { - c.loadASTComments(pkg) - } + // Load comments such as //go:extern on globals. + c.loadASTComments(pkg) // Predeclare the runtime.alloc function, which is used by the wordpack // functionality. c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function)) - // Define all functions. - var initFuncs []llvm.Value + // Compile all functions, methods, and global variables in this package. irbuilder := c.ctx.NewBuilder() defer irbuilder.Dispose() - for _, pkg := range lprogram.Sorted() { - ssaPkg := c.program.Package(pkg.Pkg) - c.createPackage(irbuilder, ssaPkg) - initFuncs = append(initFuncs, c.getFunction(ssaPkg.Members["init"].(*ssa.Function))) - } - - // After all packages are imported, add a synthetic initializer function - // that calls the initializer of each package. - initFn := c.program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function) - llvmInitFn := c.getFunction(initFn) - llvmInitFn.SetLinkage(llvm.InternalLinkage) - llvmInitFn.SetUnnamedAddr(true) - if c.Debug { - difunc := c.attachDebugInfo(initFn) - pos := c.program.Fset.Position(initFn.Pos()) - irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) - } - llvmInitFn.Param(0).SetName("context") - llvmInitFn.Param(1).SetName("parentHandle") - block := c.ctx.AddBasicBlock(llvmInitFn, "entry") - irbuilder.SetInsertPointAtEnd(block) - for _, fn := range initFuncs { - irbuilder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "") - } - irbuilder.CreateRetVoid() + c.createPackage(irbuilder, ssaPkg) // see: https://reviews.llvm.org/D18355 if c.Debug { @@ -320,22 +298,6 @@ func CompileProgram(lprogram *loader.Program, machine llvm.TargetMachine, config c.dibuilder.Finalize() } - return c.mod, c.diagnostics -} - -// CompilePackage compiles a single package to a LLVM module. -func CompilePackage(moduleName string, pkg *loader.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) { - c := newCompilerContext(moduleName, machine, config, dumpSSA) - - // Build SSA from AST. - ssaPkg := pkg.LoadSSA() - ssaPkg.Build() - - // Compile all functions, methods, and global variables in this package. - irbuilder := c.ctx.NewBuilder() - defer irbuilder.Dispose() - c.createPackage(irbuilder, ssaPkg) - return c.mod, nil } @@ -682,8 +644,9 @@ func (c *compilerContext) attachDebugInfo(f *ssa.Function) llvm.Metadata { // debug info is added to the function. func (c *compilerContext) attachDebugInfoRaw(f *ssa.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata { // Debug info for this function. - diparams := make([]llvm.Metadata, 0, len(f.Params)) - for _, param := range f.Params { + params := getParams(f.Signature) + diparams := make([]llvm.Metadata, 0, len(params)) + for _, param := range params { diparams = append(diparams, c.getDIType(param.Type())) } diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ @@ -792,7 +755,7 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package if !info.extern { global := c.getGlobal(member) global.SetInitializer(llvm.ConstNull(global.Type().ElementType())) - global.SetLinkage(llvm.InternalLinkage) + global.SetVisibility(llvm.HiddenVisibility) } } } @@ -815,7 +778,7 @@ func (b *builder) createFunction() { return } if !b.info.exported { - b.llvmFn.SetLinkage(llvm.InternalLinkage) + b.llvmFn.SetVisibility(llvm.HiddenVisibility) b.llvmFn.SetUnnamedAddr(true) } diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 3919dd81..6802d56d 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -65,8 +65,9 @@ func TestCompiler(t *testing.T) { } // Compile AST to IR. + program := lprogram.LoadSSA() pkg := lprogram.MainPkg() - mod, errs := CompilePackage(testCase, pkg, machine, compilerConfig, false) + mod, errs := CompilePackage(testCase, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false) if errs != nil { for _, err := range errs { t.Log("error:", err) diff --git a/compiler/defer.go b/compiler/defer.go index 5d356f52..f8ebb9f0 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -352,7 +352,7 @@ func (b *builder) createRunDefers() { // Get the real defer struct type and cast to it. valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} - for _, param := range callback.Params { + for _, param := range getParams(callback.Signature) { valueTypes = append(valueTypes, b.getLLVMType(param.Type())) } deferFrameType := b.ctx.StructType(valueTypes, false) @@ -361,7 +361,7 @@ func (b *builder) createRunDefers() { // Extract the params from the struct. forwardParams := []llvm.Value{} zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) - for i := range callback.Params { + for i := range getParams(callback.Signature) { gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") forwardParam := b.CreateLoad(gep, "param") forwardParams = append(forwardParams, forwardParam) diff --git a/compiler/func.go b/compiler/func.go index 37e8dac7..31162c1d 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -37,7 +37,7 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName) funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature) funcValueWithSignatureGlobal.SetGlobalConstant(true) - funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage) + funcValueWithSignatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage) } funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType) default: diff --git a/compiler/goroutine.go b/compiler/goroutine.go index 94965833..55090a9b 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -84,7 +84,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri // Create the wrapper. wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false) wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType) - wrapper.SetLinkage(llvm.InternalLinkage) + wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetUnnamedAddr(true) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name)) entry := c.ctx.AddBasicBlock(wrapper, "entry") @@ -141,7 +141,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri // Create the wrapper. wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false) wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType) - wrapper.SetLinkage(llvm.InternalLinkage) + wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetUnnamedAddr(true) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", "")) entry := c.ctx.AddBasicBlock(wrapper, "entry") diff --git a/compiler/interface.go b/compiler/interface.go index 9427c638..b21476b6 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -31,7 +31,7 @@ func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token. itfConcreteTypeGlobal = llvm.AddGlobal(b.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name()) itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal})) itfConcreteTypeGlobal.SetGlobalConstant(true) - itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage) + itfConcreteTypeGlobal.SetLinkage(llvm.LinkOnceODRLinkage) } itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "") itf := llvm.Undef(b.getLLVMRuntimeType("_interface")) @@ -80,7 +80,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1}) } global.SetInitializer(globalValue) - global.SetLinkage(llvm.PrivateLinkage) + global.SetLinkage(llvm.LinkOnceODRLinkage) } global.SetGlobalConstant(true) } @@ -264,7 +264,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset") global.SetInitializer(value) global.SetGlobalConstant(true) - global.SetLinkage(llvm.PrivateLinkage) + global.SetLinkage(llvm.LinkOnceODRLinkage) return llvm.ConstGEP(global, []llvm.Value{zero, zero}) } @@ -295,7 +295,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value { global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface") global.SetInitializer(value) global.SetGlobalConstant(true) - global.SetLinkage(llvm.PrivateLinkage) + global.SetLinkage(llvm.LinkOnceODRLinkage) return llvm.ConstGEP(global, []llvm.Value{zero, zero}) } @@ -445,7 +445,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv } // Get the expanded receiver type. - receiverType := c.getLLVMType(fn.Params[0].Type()) + receiverType := c.getLLVMType(fn.Signature.Recv().Type()) var expandedReceiverType []llvm.Type for _, info := range expandFormalParamType(receiverType, "", nil) { expandedReceiverType = append(expandedReceiverType, info.llvmType) @@ -467,7 +467,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) wrapper.LastParam().SetName("parentHandle") - wrapper.SetLinkage(llvm.InternalLinkage) + wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetUnnamedAddr(true) // Create a new builder just to create this wrapper. diff --git a/compiler/interrupt.go b/compiler/interrupt.go index 15278751..d763ce78 100644 --- a/compiler/interrupt.go +++ b/compiler/interrupt.go @@ -46,7 +46,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program") } global := llvm.AddGlobal(b.mod, globalLLVMType, globalName) - global.SetLinkage(llvm.PrivateLinkage) + global.SetVisibility(llvm.HiddenVisibility) global.SetGlobalConstant(true) global.SetUnnamedAddr(true) initializer := llvm.ConstNull(globalLLVMType) diff --git a/compiler/symbol.go b/compiler/symbol.go index ea0b01d1..9857c881 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -72,7 +72,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value { } var paramInfos []paramInfo - for _, param := range fn.Params { + for _, param := range getParams(fn.Signature) { paramType := c.getLLVMType(param.Type()) paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type()) paramInfos = append(paramInfos, paramFragmentInfos...) @@ -183,7 +183,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value { b := newBuilder(c, irbuilder, fn) b.createFunction() irbuilder.Dispose() - llvmFn.SetLinkage(llvm.InternalLinkage) + llvmFn.SetLinkage(llvm.LinkOnceODRLinkage) llvmFn.SetUnnamedAddr(true) } @@ -293,6 +293,20 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) { } } +// getParams returns the function parameters, including the receiver at the +// start. This is an alternative to the Params member of *ssa.Function, which is +// not yet populated when the package has not yet been built. +func getParams(sig *types.Signature) []*types.Var { + params := []*types.Var{} + if sig.Recv() != nil { + params = append(params, sig.Recv()) + } + for i := 0; i < sig.Params().Len(); i++ { + params = append(params, sig.Params().At(i)) + } + return params +} + // globalInfo contains some information about a specific global. By default, // linkName is equal to .RelString(nil) on a global and extern is false, but for // some symbols this is different (due to //go:extern for example). diff --git a/compiler/testdata/basic.ll b/compiler/testdata/basic.ll index dd5c9fa0..0c4f06c3 100644 --- a/compiler/testdata/basic.ll +++ b/compiler/testdata/basic.ll @@ -3,70 +3,72 @@ source_filename = "basic.go" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" target triple = "i686--linux" -define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) + +define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { entry: ret void } -define internal i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = add i32 %x, %y ret i32 %0 } -define internal i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = icmp eq i32 %x, %y ret i1 %0 } -define internal i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = fcmp oeq float %x, %y ret i1 %0 } -define internal i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = fcmp une float %x, %y ret i1 %0 } -define internal i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = fcmp olt float %x, %y ret i1 %0 } -define internal i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = fcmp ole float %x, %y ret i1 %0 } -define internal i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = fcmp ogt float %x, %y ret i1 %0 } -define internal i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = fcmp oge float %x, %y ret i1 %0 } -define internal float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr { entry: ret float %x.r } -define internal float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr { entry: ret float %x.i } -define internal { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = fadd float %x.r, %y.r %1 = fadd float %x.i, %y.i @@ -75,7 +77,7 @@ entry: ret { float, float } %3 } -define internal { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = fsub float %x.r, %y.r %1 = fsub float %x.i, %y.i @@ -84,7 +86,7 @@ entry: ret { float, float } %3 } -define internal { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = fmul float %x.r, %y.r %1 = fmul float %x.i, %y.i diff --git a/compiler/testdata/float.ll b/compiler/testdata/float.ll index 817ee091..009b92c0 100644 --- a/compiler/testdata/float.ll +++ b/compiler/testdata/float.ll @@ -3,12 +3,14 @@ source_filename = "float.go" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" target triple = "i686--linux" -define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) + +define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { entry: ret void } -define internal i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr { entry: %positive = fcmp oge float %v, 0.000000e+00 %withinmax = fcmp ole float %v, 0x41EFFFFFC0000000 @@ -19,22 +21,22 @@ entry: ret i32 %0 } -define internal float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr { +define hidden float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr { entry: ret float 0x41F0000000000000 } -define internal i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr { entry: ret i32 -1 } -define internal { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr { +define hidden { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr { entry: ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 } } -define internal i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = uitofp i32 %v to float %withinmax = fcmp ole float %0, 0x41EFFFFFC0000000 @@ -43,7 +45,7 @@ entry: ret i32 %1 } -define internal float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr { entry: %positive = fcmp oge float %v, 0.000000e+00 %withinmax = fcmp ole float %v, 0x41EFFFFFC0000000 @@ -55,7 +57,7 @@ entry: ret float %1 } -define internal i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr { entry: %positive = fcmp oge float %v, 0.000000e+00 %withinmax = fcmp ole float %v, 2.550000e+02 @@ -66,7 +68,7 @@ entry: ret i8 %0 } -define internal i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr { entry: %abovemin = fcmp oge float %v, -1.280000e+02 %belowmax = fcmp ole float %v, 1.270000e+02 diff --git a/compiler/testdata/pointer.ll b/compiler/testdata/pointer.ll index a5f4c889..26a584a9 100644 --- a/compiler/testdata/pointer.ll +++ b/compiler/testdata/pointer.ll @@ -3,46 +3,48 @@ source_filename = "pointer.go" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" target triple = "i686--linux" -define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) + +define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { entry: ret void } -define internal [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr { entry: ret [0 x i32] zeroinitializer } -define internal i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = bitcast i8* %x to i32* ret i32* %0 } -define internal i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = bitcast i32* %x to i8* ret i8* %0 } -define internal i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr { entry: ret i8* %x } -define internal i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = getelementptr inbounds i8, i8* %ptr, i32 10 ret i8* %0 } -define internal i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = getelementptr inbounds i8, i8* %ptr, i32 %offset ret i8* %0 } -define internal i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr { entry: %0 = getelementptr i32, i32* %ptr, i32 %offset ret i32* %0 diff --git a/compiler/testdata/slice.ll b/compiler/testdata/slice.ll index 72301eef..1fb054c5 100644 --- a/compiler/testdata/slice.ll +++ b/compiler/testdata/slice.ll @@ -3,17 +3,19 @@ source_filename = "slice.go" target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" target triple = "i686--linux" -define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) + +define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { entry: ret void } -define internal i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr { entry: ret i32 %ints.len } -define internal i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr { +define hidden i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr { entry: ret i32 %ints.cap } diff --git a/go.mod b/go.mod index bc9c6fe3..45900b54 100644 --- a/go.mod +++ b/go.mod @@ -12,5 +12,5 @@ require ( go.bug.st/serial v1.1.2 golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 - tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f + tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 ) diff --git a/go.sum b/go.sum index b32b598c..96850fa8 100644 --- a/go.sum +++ b/go.sum @@ -57,5 +57,5 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbO golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f h1:FP5Do5omlQ/dLQ3Hfy7oyJo69VS5Hn46rZw004r0lGU= -tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= +tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs= +tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= diff --git a/loader/loader.go b/loader/loader.go index 42347013..1eed4aae 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -2,6 +2,7 @@ package loader import ( "bytes" + "crypto/sha512" "encoding/json" "errors" "fmt" @@ -11,6 +12,7 @@ import ( "go/token" "go/types" "io" + "io/ioutil" "os" "os/exec" "path/filepath" @@ -66,10 +68,12 @@ type PackageJSON struct { type Package struct { PackageJSON - program *Program - Files []*ast.File - Pkg *types.Package - info types.Info + program *Program + Files []*ast.File + FileHashes map[string][]byte + CFlags []string // CFlags used during CGo preprocessing (only set if CGo is used) + Pkg *types.Package + info types.Info } // Load loads the given package with all dependencies (including the runtime @@ -118,7 +122,8 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t decoder := json.NewDecoder(buf) for { pkg := &Package{ - program: p, + program: p, + FileHashes: make(map[string][]byte), info: types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), @@ -277,17 +282,15 @@ func (p *Program) Parse() error { } // parseFile is a wrapper around parser.ParseFile. -func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) { - if p.fset == nil { - p.fset = token.NewFileSet() - } - - rd, err := os.Open(path) +func (p *Package) parseFile(path string, mode parser.Mode) (*ast.File, error) { + originalPath := p.program.getOriginalPath(path) + data, err := ioutil.ReadFile(path) if err != nil { return nil, err } - defer rd.Close() - return parser.ParseFile(p.fset, p.getOriginalPath(path), rd, mode) + sum := sha512.Sum512_224(data) + p.FileHashes[originalPath] = sum[:] + return parser.ParseFile(p.program.fset, originalPath, data, mode) } // Parse parses and typechecks this package. @@ -363,7 +366,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) { if !filepath.IsAbs(file) { file = filepath.Join(p.Dir, file) } - f, err := p.program.parseFile(file, parser.ParseComments) + f, err := p.parseFile(file, parser.ParseComments) if err != nil { fileErrs = append(fileErrs, err) return @@ -385,7 +388,11 @@ func (p *Package) parseFiles() ([]*ast.File, error) { if p.program.clangHeaders != "" { cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders) } - generated, ldflags, errs := cgo.Process(files, p.program.workingDir, p.program.fset, cflags) + p.CFlags = cflags + generated, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.program.fset, cflags) + for path, hash := range accessedFiles { + p.FileHashes[path] = hash + } if errs != nil { fileErrs = append(fileErrs, errs...) } diff --git a/src/internal/task/task_coroutine.go b/src/internal/task/task_coroutine.go index bfeedc30..33d910ed 100644 --- a/src/internal/task/task_coroutine.go +++ b/src/internal/task/task_coroutine.go @@ -77,22 +77,9 @@ func Current() *Task // This is implemented inside the compiler. func Pause() -type taskHolder interface { - setState(*rawState) *rawState - returnTo(*rawState) - returnCurrent() - setReturnPtr(unsafe.Pointer) - getReturnPtr() unsafe.Pointer -} - -// If there are no direct references to the task methods, they will not be discovered by the compiler, and this will trigger a compiler error. -// Instantiating this interface forces discovery of these methods. -var _ = taskHolder((*Task)(nil)) - func fake() { // Hack to ensure intrinsics are discovered. Current() - go func() {}() Pause() } diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go index 6aa3bb13..19546c58 100644 --- a/transform/interface-lowering.go +++ b/transform/interface-lowering.go @@ -173,11 +173,12 @@ func (p *lowerInterfacesPass) run() error { // Only the name of the global is relevant, the object itself is // discarded afterwards. name := global.Name() - t := &typeInfo{ - name: name, - typecode: global, + if _, ok := p.types[name]; !ok { + p.types[name] = &typeInfo{ + name: name, + typecode: global, + } } - p.types[name] = t case typeInInterfacePtr: // Count per type how often it is put in an interface. Also, collect // all methods this type has (if it is named). @@ -185,7 +186,15 @@ func (p *lowerInterfacesPass) run() error { initializer := global.Initializer() typecode := llvm.ConstExtractValue(initializer, []uint32{0}) methodSet := llvm.ConstExtractValue(initializer, []uint32{1}) - t := p.types[typecode.Name()] + typecodeName := typecode.Name() + t := p.types[typecodeName] + if t == nil { + t = &typeInfo{ + name: typecodeName, + typecode: typecode, + } + p.types[typecodeName] = t + } p.addTypeMethods(t, methodSet) // Count the number of MakeInterface instructions, for sorting the