diff --git a/.circleci/config.yml b/.circleci/config.yml index 67ae73422d..d7d9fceafc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,7 +85,7 @@ commands: key: wasi-libc-sysroot-systemclang-v1 paths: - lib/wasi-libc/sysroot - - run: go test -v -tags=llvm<> ./cgo ./compileopts ./interp ./transform . + - run: go test -v -tags=llvm<> ./cgo ./compileopts ./compiler ./interp ./transform . - run: make gen-device -j4 - run: make smoketest - save_cache: diff --git a/Makefile b/Makefile index ea68969e24..62718b95d2 100644 --- a/Makefile +++ b/Makefile @@ -111,7 +111,7 @@ endif clean: @rm -rf build -FMT_PATHS = ./*.go builder cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform +FMT_PATHS = ./*.go builder cgo compiler compiler/testdata interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform fmt: @gofmt -l -w $(FMT_PATHS) fmt-check: @@ -174,7 +174,7 @@ tinygo: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm . test: wasi-libc - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform . + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./compiler ./interp ./transform . tinygo-test: cd tests/tinygotest && tinygo test diff --git a/compiler/asserts.go b/compiler/asserts.go index e8b53f7d46..63f4b90b41 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -16,7 +16,7 @@ import ( // slice. This is required by the Go language spec: an index out of bounds must // cause a panic. func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) { - if b.fn.IsNoBounds() { + if b.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -48,7 +48,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType // biggest possible slice capacity, 'low' means len and 'high' means cap. The // logic is the same in both cases. func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { - if b.fn.IsNoBounds() { + if b.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -104,7 +104,7 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo // createChanBoundsCheck creates a bounds check before creating a new channel to // check that the value is not too big for runtime.chanMake. func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) { - if b.fn.IsNoBounds() { + if b.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -189,7 +189,7 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str // createNegativeShiftCheck creates an assertion that panics if the given shift value is negative. // This function assumes that the shift value is signed. func (b *builder) createNegativeShiftCheck(shift llvm.Value) { - if b.fn.IsNoBounds() { + if b.info.nobounds { // Function disabled bounds checking - skip shift check. return } @@ -212,8 +212,8 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc } } - faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw") - nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next") + faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw") + nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next") b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes // Now branch to the out-of-bounds or the regular block. diff --git a/compiler/calls.go b/compiler/calls.go index e5e1f44c06..294b8cd035 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -2,7 +2,9 @@ package compiler import ( "go/types" + "strconv" + "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -13,6 +15,14 @@ import ( // a struct contains more fields, it is passed as a struct without expanding. const MaxFieldsPerParam = 3 +// paramInfo contains some information collected about a function parameter, +// useful while declaring or defining a function. +type paramInfo struct { + llvmType llvm.Type + name string // name, possibly with suffixes for e.g. struct fields + flags paramFlags +} + // paramFlags identifies parameter attributes for flags. Most importantly, it // determines which parameters are dereferenceable_or_null and which aren't. type paramFlags uint8 @@ -25,14 +35,14 @@ const ( // createCall creates a new call to runtime. with the given arguments. func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value { - fullName := "runtime." + fnName - fn := b.mod.NamedFunction(fullName) - if fn.IsNil() { - panic("trying to call non-existent function: " + fullName) + fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function) + llvmFn := b.getFunction(fn) + if llvmFn.IsNil() { + panic("trying to call non-existent function: " + fn.RelString(nil)) } args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle - return b.createCall(fn, args, name) + return b.createCall(llvmFn, args, name) } // createCall creates a call to the given function with the arguments possibly @@ -48,19 +58,23 @@ func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm // Expand an argument type to a list that can be used in a function call // parameter list. -func expandFormalParamType(t llvm.Type, goType types.Type) ([]llvm.Type, []paramFlags) { +func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo { switch t.TypeKind() { case llvm.StructTypeKind: - fields, fieldFlags := flattenAggregateType(t, goType) - if len(fields) <= MaxFieldsPerParam { - return fields, fieldFlags + fieldInfos := flattenAggregateType(t, name, goType) + if len(fieldInfos) <= MaxFieldsPerParam { + return fieldInfos } else { // failed to lower - return []llvm.Type{t}, []paramFlags{getTypeFlags(goType)} } - default: - // TODO: split small arrays - return []llvm.Type{t}, []paramFlags{getTypeFlags(goType)} + } + // TODO: split small arrays + return []paramInfo{ + { + llvmType: t, + name: name, + flags: getTypeFlags(goType), + }, } } @@ -91,10 +105,10 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 { func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value { switch v.Type().TypeKind() { case llvm.StructTypeKind: - fieldTypes, _ := flattenAggregateType(v.Type(), nil) - if len(fieldTypes) <= MaxFieldsPerParam { + fieldInfos := flattenAggregateType(v.Type(), "", nil) + if len(fieldInfos) <= MaxFieldsPerParam { fields := b.flattenAggregate(v) - if len(fields) != len(fieldTypes) { + if len(fields) != len(fieldInfos) { panic("type and value param lowering don't match") } return fields @@ -110,23 +124,49 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value { // Try to flatten a struct type to a list of types. Returns a 1-element slice // with the passed in type if this is not possible. -func flattenAggregateType(t llvm.Type, goType types.Type) ([]llvm.Type, []paramFlags) { +func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo { typeFlags := getTypeFlags(goType) switch t.TypeKind() { case llvm.StructTypeKind: - fields := make([]llvm.Type, 0, t.StructElementTypesCount()) - fieldFlags := make([]paramFlags, 0, cap(fields)) + paramInfos := make([]paramInfo, 0, t.StructElementTypesCount()) for i, subfield := range t.StructElementTypes() { - subfields, subfieldFlags := flattenAggregateType(subfield, extractSubfield(goType, i)) - for i := range subfieldFlags { - subfieldFlags[i] |= typeFlags + suffix := strconv.Itoa(i) + if goType != nil { + // Try to come up with a good suffix for this struct field, + // depending on which Go type it's based on. + switch goType := goType.Underlying().(type) { + case *types.Interface: + suffix = []string{"typecode", "value"}[i] + case *types.Slice: + suffix = []string{"data", "len", "cap"}[i] + case *types.Struct: + suffix = goType.Field(i).Name() + case *types.Basic: + switch goType.Kind() { + case types.Complex64, types.Complex128: + suffix = []string{"r", "i"}[i] + case types.String: + suffix = []string{"data", "len"}[i] + } + case *types.Signature: + suffix = []string{"context", "funcptr"}[i] + } } - fields = append(fields, subfields...) - fieldFlags = append(fieldFlags, subfieldFlags...) + subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i)) + for i := range subInfos { + subInfos[i].flags |= typeFlags + } + paramInfos = append(paramInfos, subInfos...) } - return fields, fieldFlags + return paramInfos default: - return []llvm.Type{t}, []paramFlags{typeFlags} + return []paramInfo{ + { + llvmType: t, + name: name, + flags: typeFlags, + }, + } } } @@ -226,7 +266,7 @@ func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Val func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) { switch t.TypeKind() { case llvm.StructTypeKind: - flattened, _ := flattenAggregateType(t, nil) + flattened := flattenAggregateType(t, "", nil) if len(flattened) <= MaxFieldsPerParam { value := llvm.ConstNull(t) for i, subtyp := range t.StructElementTypes() { diff --git a/compiler/compiler.go b/compiler/compiler.go index 0464d11059..8112fe0b44 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -11,13 +11,13 @@ import ( "go/types" "os" "path/filepath" + "sort" "strconv" "strings" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/goenv" - "github.com/tinygo-org/tinygo/ir" "github.com/tinygo-org/tinygo/loader" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" @@ -51,7 +51,7 @@ type compilerContext struct { i8ptrType llvm.Type // for convenience funcPtrAddrSpace int uintptrType llvm.Type - ir *ir.Program + program *ssa.Program diagnostics []error astComments map[string]*ast.CommentGroup } @@ -60,7 +60,9 @@ type compilerContext struct { type builder struct { *compilerContext llvm.Builder - fn *ir.Function + fn *ssa.Function + llvmFn llvm.Value + info functionInfo locals map[ssa.Value]llvm.Value // local variables blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks @@ -71,9 +73,9 @@ type builder struct { difunc llvm.Metadata dilocals map[*types.Var]llvm.Metadata allDeferFuncs []interface{} - deferFuncs map[*ir.Function]int + deferFuncs map[*ssa.Function]int deferInvokeFuncs map[string]int - deferClosureFuncs map[*ir.Function]int + deferClosureFuncs map[*ssa.Function]int selectRecvBuf map[*ssa.Select]llvm.Value } @@ -95,15 +97,9 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) { return machine, nil } -// Compile the given package path or .go file path. Return an error when this -// fails (in any stage). If successful it returns the LLVM module and a list of -// extra C files to be compiled. If not, one or more errors will be returned. -// -// The fact that it returns a list of filenames to compile is a layering -// violation. Eventually, this Compile function should only compile a single -// package and not the whole program, and loading of the program (including CGo -// processing) should be moved outside the compiler package. -func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []string, []error) { +// newCompilerContext builds a new *compilerContext based on the provided +// configuration, ready to compile Go SSA to LLVM IR. +func newCompilerContext(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) *compilerContext { c := &compilerContext{ Config: config, difiles: make(map[string]llvm.Metadata), @@ -137,6 +133,20 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() dummyFunc.EraseFromParentAsFunction() + return c +} + +// Compile the given package path or .go file path. Return an error when this +// fails (in any stage). If successful it returns the LLVM module and a list of +// extra C files to be compiled. If not, one or more errors will be returned. +// +// The fact that it returns a list of filenames to compile is a layering +// violation. Eventually, this Compile function should only compile a single +// package and not the whole program, and loading of the program (including CGo +// processing) should be moved outside the compiler package. +func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []string, []error) { + c := newCompilerContext(pkgName, machine, config) + // Prefix the GOPATH with the system GOROOT, as GOROOT is already set to // the TinyGo root. overlayGopath := goenv.Get("GOPATH") @@ -234,13 +244,8 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con return c.mod, nil, []error{err} } - c.ir = ir.NewProgram(lprogram, pkgName) - - // Run a simple dead code elimination pass. - err = c.ir.SimpleDCE() - if err != nil { - return c.mod, nil, []error{err} - } + c.program = lprogram.LoadSSA() + c.program.Build() // Initialize debug information. if c.Debug() { @@ -259,7 +264,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con // TODO: lazily create runtime types in getLLVMRuntimeType when they are // needed. Eventually this will be required anyway, when packages are // compiled independently (and the runtime types are not available). - for _, member := range c.ir.Program.ImportedPackage("runtime").Members { + for _, member := range c.program.ImportedPackage("runtime").Members { if member, ok := member.(*ssa.Type); ok { if typ, ok := member.Type().(*types.Named); ok { if _, ok := typ.Underlying().(*types.Struct); ok { @@ -269,50 +274,44 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con } } - // Declare all functions. - for _, f := range c.ir.Functions { - c.createFunctionDeclaration(f) + // Predeclare the runtime.alloc function, which is used by the wordpack + // functionality. + c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function)) + + sortedPackages := sortPackages(c.program, pkgName) + + // Find package initializers. + var initFuncs []llvm.Value + for _, pkg := range sortedPackages { + for _, member := range pkg.Members { + switch member := member.(type) { + case *ssa.Function: + if member.Synthetic == "package initializer" { + initFuncs = append(initFuncs, c.getFunction(member)) + } + } + } } // Add definitions to declarations. - var initFuncs []llvm.Value irbuilder := c.ctx.NewBuilder() defer irbuilder.Dispose() - for _, f := range c.ir.Functions { - if f.Synthetic == "package initializer" { - initFuncs = append(initFuncs, f.LLVMFn) - } - if f.CName() != "" { - continue - } - if f.Blocks == nil { - continue // external function - } - - // Create the function definition. - b := builder{ - compilerContext: c, - Builder: irbuilder, - fn: f, - locals: make(map[ssa.Value]llvm.Value), - dilocals: make(map[*types.Var]llvm.Metadata), - blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), - blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock), - } - b.createFunctionDefinition() + for _, pkg := range sortedPackages { + c.createPackage(pkg, irbuilder) } // After all packages are imported, add a synthetic initializer function // that calls the initializer of each package. - initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)) - initFn.LLVMFn.SetLinkage(llvm.InternalLinkage) - initFn.LLVMFn.SetUnnamedAddr(true) + 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.ir.Program.Fset.Position(initFn.Pos()) + pos := c.program.Fset.Position(initFn.Pos()) irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } - block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry") + 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)}, "") @@ -321,7 +320,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con // Conserve for goroutine lowering. Without marking these as external, they // would be optimized away. - realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main") + realMain := c.mod.NamedFunction(pkgName + ".main") realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering // Replace callMain placeholder with actual main function. @@ -377,7 +376,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con // Gather the list of (C) file paths that should be included in the build. var extraFiles []string - for _, pkg := range c.ir.LoaderProgram.Sorted() { + for _, pkg := range lprogram.Sorted() { for _, file := range pkg.CFiles { extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file)) } @@ -386,6 +385,73 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con return c.mod, extraFiles, c.diagnostics } +// sortPackages returns a list of all packages, sorted by import order. +func sortPackages(program *ssa.Program, mainPath string) []*ssa.Package { + // Find the main package, which is a bit difficult when running a .go file + // directly. + mainPkg := program.ImportedPackage(mainPath) + if mainPkg == nil { + for _, pkgInfo := range program.AllPackages() { + if pkgInfo.Pkg.Name() == "main" { + if mainPkg != nil { + panic("more than one main package found") + } + mainPkg = pkgInfo + } + } + } + if mainPkg == nil { + panic("could not find main package") + } + + packageList := []*ssa.Package{} + packageSet := map[string]struct{}{} + worklist := []string{"runtime", mainPath} + for len(worklist) != 0 { + pkgPath := worklist[0] + var pkg *ssa.Package + if pkgPath == mainPath { + pkg = mainPkg // necessary for compiling individual .go files + } else { + pkg = program.ImportedPackage(pkgPath) + } + if pkg == nil { + // Non-SSA package (e.g. cgo). + packageSet[pkgPath] = struct{}{} + worklist = worklist[1:] + continue + } + if _, ok := packageSet[pkgPath]; ok { + // Package already in the final package list. + worklist = worklist[1:] + continue + } + + unsatisfiedImports := make([]string, 0) + imports := pkg.Pkg.Imports() + for _, pkg := range imports { + if _, ok := packageSet[pkg.Path()]; ok { + continue + } + unsatisfiedImports = append(unsatisfiedImports, pkg.Path()) + } + if len(unsatisfiedImports) == 0 { + // All dependencies of this package are satisfied, so add this + // package to the list. + packageList = append(packageList, pkg) + packageSet[pkgPath] = struct{}{} + worklist = worklist[1:] + } else { + // Prepend all dependencies to the worklist and reconsider this + // package (by not removing it from the worklist). At that point, it + // must be possible to add it to packageList. + worklist = append(unsatisfiedImports, worklist...) + } + } + + return packageList +} + // getLLVMRuntimeType obtains a named type from the runtime package and returns // it as a LLVM type, creating it if necessary. It is a shorthand for // getLLVMType(getRuntimeType(name)). @@ -579,11 +645,11 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata { Encoding: encoding, }) case *types.Chan: - return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type())) + return c.getDIType(types.NewPointer(c.program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type())) case *types.Interface: - return c.getDIType(c.ir.Program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type()) + return c.getDIType(c.program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type()) case *types.Map: - return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type())) + return c.getDIType(types.NewPointer(c.program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type())) case *types.Named: return c.dibuilder.CreateTypedef(llvm.DITypedef{ Type: c.getDIType(typ.Underlying()), @@ -691,7 +757,7 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata { return dilocal } - pos := b.ir.Program.Fset.Position(variable.Pos()) + pos := b.program.Fset.Position(variable.Pos()) // Check whether this is a function parameter. for i, param := range b.fn.Params { @@ -722,93 +788,17 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata { return dilocal } -// createFunctionDeclaration creates a LLVM function declaration without body. -// It can later be filled with frame.createFunctionDefinition(). -func (c *compilerContext) createFunctionDeclaration(f *ir.Function) { - var retType llvm.Type - if f.Signature.Results() == nil { - retType = c.ctx.VoidType() - } else if f.Signature.Results().Len() == 1 { - retType = c.getLLVMType(f.Signature.Results().At(0).Type()) - } else { - results := make([]llvm.Type, 0, f.Signature.Results().Len()) - for i := 0; i < f.Signature.Results().Len(); i++ { - results = append(results, c.getLLVMType(f.Signature.Results().At(i).Type())) - } - retType = c.ctx.StructType(results, false) - } - - var paramTypes []llvm.Type - var paramTypeVariants []paramFlags - for _, param := range f.Params { - paramType := c.getLLVMType(param.Type()) - paramTypeFragments, paramTypeFragmentVariants := expandFormalParamType(paramType, param.Type()) - paramTypes = append(paramTypes, paramTypeFragments...) - paramTypeVariants = append(paramTypeVariants, paramTypeFragmentVariants...) - } - - // Add an extra parameter as the function context. This context is used in - // closures and bound methods, but should be optimized away when not used. - if !f.IsExported() { - paramTypes = append(paramTypes, c.i8ptrType) // context - paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine - paramTypeVariants = append(paramTypeVariants, 0, 0) - } - - fnType := llvm.FunctionType(retType, paramTypes, false) - - name := f.LinkName() - f.LLVMFn = c.mod.NamedFunction(name) - if f.LLVMFn.IsNil() { - f.LLVMFn = llvm.AddFunction(c.mod, name, fnType) - } - - dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") - for i, typ := range paramTypes { - if paramTypeVariants[i]¶mIsDeferenceableOrNull == 0 { - continue - } - if typ.TypeKind() == llvm.PointerTypeKind { - el := typ.ElementType() - size := c.targetData.TypeAllocSize(el) - if size == 0 { - // dereferenceable_or_null(0) appears to be illegal in LLVM. - continue - } - dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size) - f.LLVMFn.AddAttributeAtIndex(i+1, dereferenceableOrNull) - } - } - - // External/exported functions may not retain pointer values. - // https://golang.org/cmd/cgo/#hdr-Passing_pointers - if f.IsExported() { - // Set the wasm-import-module attribute if the function's module is set. - if f.Module() != "" { - wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", f.Module()) - f.LLVMFn.AddFunctionAttr(wasmImportModuleAttr) - } - nocaptureKind := llvm.AttributeKindID("nocapture") - nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) - for i, typ := range paramTypes { - if typ.TypeKind() == llvm.PointerTypeKind { - f.LLVMFn.AddAttributeAtIndex(i+1, nocapture) - } - } - } -} - // attachDebugInfo adds debug info to a function declaration. It returns the // DISubprogram metadata node. -func (c *compilerContext) attachDebugInfo(f *ir.Function) llvm.Metadata { - pos := c.ir.Program.Fset.Position(f.Syntax().Pos()) - return c.attachDebugInfoRaw(f, f.LLVMFn, "", pos.Filename, pos.Line) +func (c *compilerContext) attachDebugInfo(f *ssa.Function) llvm.Metadata { + pos := c.program.Fset.Position(f.Syntax().Pos()) + return c.attachDebugInfoRaw(f, c.getFunction(f), "", pos.Filename, pos.Line) } // attachDebugInfo adds debug info to a function declaration. It returns the // DISubprogram metadata node. This method allows some more control over how // debug info is added to the function. -func (c *compilerContext) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata { +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 { @@ -821,7 +811,7 @@ func (c *compilerContext) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, }) difunc := c.dibuilder.CreateFunction(c.getDIFile(filename), llvm.DIFunction{ Name: f.RelString(nil) + suffix, - LinkageName: f.LinkName() + suffix, + LinkageName: c.getFunctionInfo(f).linkName + suffix, File: c.getDIFile(filename), Line: line, Type: diFuncType, @@ -849,37 +839,99 @@ func (c *compilerContext) getDIFile(filename string) llvm.Metadata { return c.difiles[filename] } -// createFunctionDefinition builds the LLVM IR implementation for this function. -// The function must be declared but not yet defined, otherwise this function -// will create a diagnostic. -func (b *builder) createFunctionDefinition() { +func (c *compilerContext) createPackage(pkg *ssa.Package, irbuilder llvm.Builder) { + memberNames := make([]string, 0) + for name := range pkg.Members { + memberNames = append(memberNames, name) + } + sort.Strings(memberNames) + + for _, name := range memberNames { + switch member := pkg.Members[name].(type) { + case *ssa.Function: + llvmFn := c.getFunction(member) + if member.Blocks == nil { + continue // external function + } + c.createFunction(irbuilder, member, llvmFn) + case *ssa.Type: + if types.IsInterface(member.Type()) { + // Interfaces don't have concrete methods. + continue + } + + // Named type. We should make sure all methods are created. + // This includes both functions with pointer receivers and those + // without. + methods := getAllMethods(pkg.Prog, member.Type()) + methods = append(methods, getAllMethods(pkg.Prog, types.NewPointer(member.Type()))...) + for _, method := range methods { + // Parse this method. + fn := pkg.Prog.MethodValue(method) + if fn.Blocks == nil { + continue // external function + } + if member.Type().String() != member.String() { + // This is a member on a type alias. Do not build such a + // function. + continue + } + c.createFunction(irbuilder, fn, c.getFunction(fn)) + } + case *ssa.Global: + // Make sure the global is present and has an initializer. + c.getGlobal(member) + case *ssa.NamedConst: + // TODO: create DWARF entries for these. + default: + panic("unknown member type: " + member.String()) + } + } +} + +// createFunction builds the LLVM IR implementation for this function. The +// function must not yet be defined, otherwise this function will create a +// diagnostic. +func (c *compilerContext) createFunction(irbuilder llvm.Builder, fn *ssa.Function, llvmFn llvm.Value) { + b := builder{ + compilerContext: c, + Builder: irbuilder, + fn: fn, + llvmFn: llvmFn, + info: c.getFunctionInfo(fn), + locals: make(map[ssa.Value]llvm.Value), + dilocals: make(map[*types.Var]llvm.Metadata), + blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), + blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock), + } + if b.DumpSSA() { - fmt.Printf("\nfunc %s:\n", b.fn.Function) + fmt.Printf("\nfunc %s:\n", b.fn) } - if !b.fn.LLVMFn.IsDeclaration() { + if !b.llvmFn.IsDeclaration() { errValue := b.fn.Name() + " redeclared in this program" - fnPos := getPosition(b.fn.LLVMFn) + fnPos := getPosition(b.llvmFn) if fnPos.IsValid() { errValue += "\n\tprevious declaration at " + fnPos.String() } b.addError(b.fn.Pos(), errValue) return } - if !b.fn.IsExported() { - b.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) - b.fn.LLVMFn.SetUnnamedAddr(true) + if !b.info.exported { + b.llvmFn.SetLinkage(llvm.InternalLinkage) + b.llvmFn.SetUnnamedAddr(true) } // Some functions have a pragma controlling the inlining level. - switch b.fn.Inline() { - case ir.InlineHint: + switch b.info.inline { + case inlineHint: // Add LLVM inline hint to functions with //go:inline pragma. inline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0) - b.fn.LLVMFn.AddFunctionAttr(inline) - case ir.InlineNone: + b.llvmFn.AddFunctionAttr(inline) + case inlineNone: // Add LLVM attribute to always avoid inlining this function. noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0) - b.fn.LLVMFn.AddFunctionAttr(noinline) + b.llvmFn.AddFunctionAttr(noinline) } // Add debug info, if needed. @@ -888,18 +940,18 @@ func (b *builder) createFunctionDefinition() { // Package initializers have no debug info. Create some fake debug // info to at least have *something*. filename := b.fn.Package().Pkg.Path() + "/" - b.difunc = b.attachDebugInfoRaw(b.fn, b.fn.LLVMFn, "", filename, 0) + b.difunc = b.attachDebugInfoRaw(b.fn, b.llvmFn, "", filename, 0) } else if b.fn.Syntax() != nil { // Create debug info file if needed. b.difunc = b.attachDebugInfo(b.fn) } - pos := b.ir.Program.Fset.Position(b.fn.Pos()) + pos := b.program.Fset.Position(b.fn.Pos()) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } // Pre-create all basic blocks in the function. for _, block := range b.fn.DomPreorder() { - llvmBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, block.Comment) + llvmBlock := b.ctx.AddBasicBlock(b.llvmFn, block.Comment) b.blockEntries[block] = llvmBlock b.blockExits[block] = llvmBlock } @@ -911,9 +963,10 @@ func (b *builder) createFunctionDefinition() { for _, param := range b.fn.Params { llvmType := b.getLLVMType(param.Type()) fields := make([]llvm.Value, 0, 1) - fieldFragments, _ := expandFormalParamType(llvmType, nil) - for range fieldFragments { - fields = append(fields, b.fn.LLVMFn.Param(llvmParamIndex)) + for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) { + param := b.llvmFn.Param(llvmParamIndex) + param.SetName(info.name) + fields = append(fields, param) llvmParamIndex++ } b.locals[param] = b.collapseFormalParam(llvmType, fields) @@ -942,8 +995,8 @@ func (b *builder) createFunctionDefinition() { // Load free variables from the context. This is a closure (or bound // method). var context llvm.Value - if !b.fn.IsExported() { - parentHandle := b.fn.LLVMFn.LastParam() + if !b.info.exported { + parentHandle := b.llvmFn.LastParam() parentHandle.SetName("parentHandle") context = llvm.PrevParam(parentHandle) context.SetName("context") @@ -994,7 +1047,7 @@ func (b *builder) createFunctionDefinition() { continue } dbgVar := b.getLocalVariable(variable) - pos := b.ir.Program.Fset.Position(instr.Pos()) + pos := b.program.Fset.Position(instr.Pos()) b.dibuilder.InsertValueAtEnd(b.getValue(instr.X), dbgVar, b.dibuilder.CreateExpression(nil), llvm.DebugLoc{ Line: uint(pos.Line), Col: uint(pos.Column), @@ -1037,13 +1090,18 @@ func (b *builder) createFunctionDefinition() { b.trackValue(phi.llvm) } } + + // Compile all anonymous functions part of this function. + for _, fn := range b.fn.AnonFuncs { + b.createFunction(b.Builder, fn, b.getFunction(fn)) + } } // createInstruction builds the LLVM IR equivalent instructions for the // particular Go SSA instruction. func (b *builder) createInstruction(instr ssa.Instruction) { if b.Debug() { - pos := b.ir.Program.Fset.Position(instr.Pos()) + pos := b.program.Fset.Position(instr.Pos()) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } @@ -1079,7 +1137,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) { if callee := instr.Call.StaticCallee(); callee != nil { // Static callee is known. This makes it easier to start a new // goroutine. - calleeFn := b.ir.GetFunction(callee) + calleeFn := b.getFunction(callee) var context llvm.Value switch value := instr.Call.Value.(type) { case *ssa.Function: @@ -1094,7 +1152,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) { panic("StaticCallee returned an unexpected value") } params = append(params, context) // context parameter - b.createGoInstruction(calleeFn.LLVMFn, params, "", callee.Pos()) + b.createGoInstruction(calleeFn, params, "", callee.Pos()) } else if !instr.Call.IsInvoke() { // This is a function pointer. // At the moment, two extra params are passed to the newly started @@ -1142,7 +1200,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) { b.CreateRet(b.getValue(instr.Results[0])) } else { // Multiple return values. Put them all in a struct. - retVal := llvm.ConstNull(b.fn.LLVMFn.Type().ElementType().ReturnType()) + retVal := llvm.ConstNull(b.llvmFn.Type().ElementType().ReturnType()) for i, result := range instr.Results { val := b.getValue(result) retVal = b.CreateInsertValue(retVal, val, i, "") @@ -1370,7 +1428,15 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) case strings.HasPrefix(name, "device/arm.SVCall"): return b.emitSVCall(instr.Args) case strings.HasPrefix(name, "(device/riscv.CSR)."): - return b.emitCSROperation(instr) + // The device/riscv.CSR operations must happen on constants. + // However, the compiler also creates pointer receivers for this + // operation which do not provide constant parameters. Therefore, do + // not create the regular inline asm for such functions but leave + // the call to an undefined function instead. + recv := b.fn.Signature.Recv() + if recv == nil || recv.Type().String() != "*device/riscv.CSR" { + return b.emitCSROperation(instr) + } case strings.HasPrefix(name, "syscall.Syscall"): return b.createSyscall(instr) case strings.HasPrefix(name, "runtime/volatile.Load"): @@ -1381,9 +1447,10 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return b.createInterruptGlobal(instr) } - targetFunc := b.ir.GetFunction(fn) - if targetFunc.LLVMFn.IsNil() { - return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName()) + callee = b.getFunction(fn) + info := b.getFunctionInfo(fn) + if callee.IsNil() { + return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+info.linkName) } switch value := instr.Value.(type) { case *ssa.Function: @@ -1397,8 +1464,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) default: panic("StaticCallee returned an unexpected value") } - callee = targetFunc.LLVMFn - exported = targetFunc.IsExported() + exported = info.exported } else if call, ok := instr.Value.(*ssa.Builtin); ok { // Builtin function (append, close, delete, etc.).) return b.createBuiltin(instr.Args, call.Name(), instr.Pos()) @@ -1433,14 +1499,15 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) func (b *builder) getValue(expr ssa.Value) llvm.Value { switch expr := expr.(type) { case *ssa.Const: - return b.createConst(b.fn.LinkName(), expr) + return b.createConst(b.info.linkName, expr) case *ssa.Function: - fn := b.ir.GetFunction(expr) - if fn.IsExported() { + info := b.getFunctionInfo(expr) + if info.exported { b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String()) return llvm.Undef(b.getLLVMType(expr.Type())) } - return b.createFuncValue(fn.LLVMFn, llvm.Undef(b.i8ptrType), fn.Signature) + llvmFn := b.getFunction(expr) + return b.createFuncValue(llvmFn, llvm.Undef(b.i8ptrType), expr.Signature) case *ssa.Global: value := b.getGlobal(expr) if value.IsNil() { diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go new file mode 100644 index 0000000000..1607f624e5 --- /dev/null +++ b/compiler/compiler_test.go @@ -0,0 +1,136 @@ +package compiler + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "io/ioutil" + "path/filepath" + "strings" + "sync" + "testing" + + "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/compiler/ircheck" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" + "tinygo.org/x/go-llvm" +) + +var flagUpdate = flag.Bool("update", false, "update all tests") + +func TestCompiler(t *testing.T) { + t.Parallel() + for _, name := range []string{"basic"} { + t.Run(name, func(t *testing.T) { + runCompilerTest(t, name) + }) + } +} + +func runCompilerTest(t *testing.T, name string) { + // Read the AST in memory. + path := filepath.Join("testdata", name+".go") + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, path, nil, parser.ParseComments) + if err != nil { + t.Fatal("could not parse Go source file:", err) + } + files := []*ast.File{f} + + // Create Go SSA from the AST. + var typecheckErrors []error + var typecheckErrorsLock sync.Mutex + typesConfig := types.Config{ + Error: func(err error) { + typecheckErrorsLock.Lock() + defer typecheckErrorsLock.Unlock() + typecheckErrors = append(typecheckErrors, err) + }, + Importer: simpleImporter{}, + Sizes: types.SizesFor("gccgo", "arm"), + } + pkg, _, err := ssautil.BuildPackage(&typesConfig, fset, types.NewPackage("main", ""), files, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug) + for _, err := range typecheckErrors { + t.Error(err) + } + if err != nil && len(typecheckErrors) == 0 { + // Only report errors when no type errors are found (an + // unexpected condition). + t.Error(err) + } + if t.Failed() { + return + } + + // Configure the compiler. + config := compileopts.Config{ + Options: &compileopts.Options{}, + Target: &compileopts.TargetSpec{ + Triple: "armv7m-none-eabi", + BuildTags: []string{"cortexm", "baremetal", "linux", "arm"}, + Scheduler: "tasks", + }, + } + machine, err := NewTargetMachine(&config) + if err != nil { + t.Fatal(err) + } + c := newCompilerContext("main", machine, &config) + irbuilder := c.ctx.NewBuilder() + defer irbuilder.Dispose() + + // Create LLVM IR from the Go SSA. + c.createPackage(pkg, irbuilder) + + // Check the IR with the LLVM verifier. + if err := llvm.VerifyModule(c.mod, llvm.PrintMessageAction); err != nil { + t.Error("verification error after IR construction") + } + + // Check the IR with our own verifier (which checks for different things). + errs := ircheck.Module(c.mod) + for _, err := range errs { + t.Error(err) + } + + // Check whether the IR matches the expected IR. + ir := c.mod.String() + ir = ir[strings.Index(ir, "\ntarget datalayout = ")+1:] + outfile := filepath.Join("testdata", name+".ll") + if *flagUpdate { + err := ioutil.WriteFile(outfile, []byte(ir), 0666) + if err != nil { + t.Error("could not read output file:", err) + } + } else { + ir2, err := ioutil.ReadFile(outfile) + if err != nil { + t.Fatal("could not read input file:", err) + } + ir2 = bytes.Replace(ir2, []byte("\r\n"), []byte("\n"), -1) + if ir != string(ir2) { + t.Error("output did not match") + } + } +} + +// simpleImporter implements the types.Importer interface, but only allows +// importing the unsafe package. +type simpleImporter struct { +} + +// Import implements the Importer interface. For testing usage only: it only +// supports importing the unsafe package. +func (i simpleImporter) Import(path string) (*types.Package, error) { + switch path { + case "unsafe": + return types.Unsafe, nil + default: + return nil, fmt.Errorf("importer not implemented for package %s", path) + } +} diff --git a/compiler/defer.go b/compiler/defer.go index 4a2a480a81..e4c78f3a36 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -15,7 +15,6 @@ package compiler import ( "github.com/tinygo-org/tinygo/compiler/llvmutil" - "github.com/tinygo-org/tinygo/ir" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -25,9 +24,9 @@ import ( // calls. func (b *builder) deferInitFunc() { // Some setup. - b.deferFuncs = make(map[*ir.Function]int) + b.deferFuncs = make(map[*ssa.Function]int) b.deferInvokeFuncs = make(map[string]int) - b.deferClosureFuncs = make(map[*ir.Function]int) + b.deferClosureFuncs = make(map[*ssa.Function]int) // Create defer list pointer. deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0) @@ -104,13 +103,12 @@ func (b *builder) createDefer(instr *ssa.Defer) { } else if callee, ok := instr.Call.Value.(*ssa.Function); ok { // Regular function call. - fn := b.ir.GetFunction(callee) - if _, ok := b.deferFuncs[fn]; !ok { - b.deferFuncs[fn] = len(b.allDeferFuncs) - b.allDeferFuncs = append(b.allDeferFuncs, fn) + if _, ok := b.deferFuncs[callee]; !ok { + b.deferFuncs[callee] = len(b.allDeferFuncs) + b.allDeferFuncs = append(b.allDeferFuncs, callee) } - callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false) + callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false) // Collect all values to be put in the struct (starting with // runtime._defer fields). @@ -132,7 +130,7 @@ func (b *builder) createDefer(instr *ssa.Defer) { context := b.CreateExtractValue(closure, 0, "") // Get the callback number. - fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function)) + fn := makeClosure.Fn.(*ssa.Function) if _, ok := b.deferClosureFuncs[fn]; !ok { b.deferClosureFuncs[fn] = len(b.allDeferFuncs) b.allDeferFuncs = append(b.allDeferFuncs, makeClosure) @@ -203,10 +201,10 @@ func (b *builder) createRunDefers() { // } // Create loop. - loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead") - loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop") - unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default") - end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end") + loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead") + loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop") + unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default") + end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end") b.CreateBr(loophead) // Create loop head: @@ -238,7 +236,7 @@ func (b *builder) createRunDefers() { // Create switch case, for example: // case 0: // // run first deferred call - block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback") + block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback") sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block) b.SetInsertPointAtEnd(block) switch callback := callback.(type) { @@ -279,7 +277,7 @@ func (b *builder) createRunDefers() { fnPtr := b.getInvokePtr(callback, typecode) b.createCall(fnPtr, forwardParams, "") - case *ir.Function: + case *ssa.Function: // Direct call. // Get the real defer struct type and cast to it. @@ -301,7 +299,7 @@ func (b *builder) createRunDefers() { // Plain TinyGo functions add some extra parameters to implement async functionality and function recievers. // These parameters should not be supplied when calling into an external C/ASM function. - if !callback.IsExported() { + if !b.getFunctionInfo(callback).exported { // Add the context parameter. We know it is ignored by the receiving // function, but we have to pass one anyway. forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) @@ -311,11 +309,11 @@ func (b *builder) createRunDefers() { } // Call real function. - b.createCall(callback.LLVMFn, forwardParams, "") + b.createCall(b.getFunction(callback), forwardParams, "") case *ssa.MakeClosure: // Get the real defer struct type and cast to it. - fn := b.ir.GetFunction(callback.Fn.(*ssa.Function)) + fn := callback.Fn.(*ssa.Function) valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} params := fn.Signature.Params() for i := 0; i < params.Len(); i++ { @@ -338,7 +336,7 @@ func (b *builder) createRunDefers() { forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) // Call deferred function. - b.createCall(fn.LLVMFn, forwardParams, "") + b.createCall(b.getFunction(fn), forwardParams, "") default: panic("unknown deferred function type") diff --git a/compiler/errors.go b/compiler/errors.go index 85de4d161e..1181322980 100644 --- a/compiler/errors.go +++ b/compiler/errors.go @@ -14,7 +14,7 @@ import ( // makeError makes it easy to create an error from a token.Pos with a message. func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error { return types.Error{ - Fset: c.ir.Program.Fset, + Fset: c.program.Fset, Pos: pos, Msg: msg, } diff --git a/compiler/func.go b/compiler/func.go index 2d14d47a20..08bb31e9eb 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -5,6 +5,7 @@ package compiler import ( "go/types" + "strings" "github.com/tinygo-org/tinygo/compileopts" "golang.org/x/tools/go/ssa" @@ -125,13 +126,15 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type { // The receiver is not an interface, but a i8* type. recv = c.i8ptrType } - recvFragments, _ := expandFormalParamType(recv, nil) - paramTypes = append(paramTypes, recvFragments...) + for _, info := range expandFormalParamType(recv, "", nil) { + paramTypes = append(paramTypes, info.llvmType) + } } for i := 0; i < typ.Params().Len(); i++ { subType := c.getLLVMType(typ.Params().At(i).Type()) - paramTypeFragments, _ := expandFormalParamType(subType, nil) - paramTypes = append(paramTypes, paramTypeFragments...) + for _, info := range expandFormalParamType(subType, "", nil) { + paramTypes = append(paramTypes, info.llvmType) + } } // All functions take these parameters at the end. paramTypes = append(paramTypes, c.i8ptrType) // context @@ -147,7 +150,16 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) { if len(expr.Bindings) == 0 { panic("unexpected: MakeClosure without bound variables") } - f := b.ir.GetFunction(expr.Fn.(*ssa.Function)) + f := expr.Fn.(*ssa.Function) + llvmFn := b.getFunction(f) + + if strings.HasSuffix(f.Name(), "$bound") && llvmFn.IsDeclaration() { + // Hack: the ssa package does not expose bound methods so make sure + // they're built here when necessary. + irbuilder := b.ctx.NewBuilder() + defer irbuilder.Dispose() + b.createFunction(irbuilder, f, llvmFn) + } // Collect all bound variables. boundVars := make([]llvm.Value, len(expr.Bindings)) @@ -162,5 +174,5 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) { context := b.emitPointerPack(boundVars) // Create the closure. - return b.createFuncValue(f.LLVMFn, context, f.Signature), nil + return b.createFuncValue(llvmFn, context, f.Signature), nil } diff --git a/compiler/goroutine.go b/compiler/goroutine.go index bb476e7c91..1863fcb2bb 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -7,6 +7,7 @@ import ( "go/token" "github.com/tinygo-org/tinygo/compiler/llvmutil" + "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -28,7 +29,8 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p default: panic("unreachable") } - b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "") + start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) + b.createCall(start, []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "") return llvm.Undef(funcPtr.Type().ElementType().ReturnType()) } @@ -73,7 +75,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri builder.SetInsertPointAtEnd(entry) if c.Debug() { - pos := c.ir.Program.Fset.Position(pos) + pos := c.program.Fset.Position(pos) diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ File: c.getDIFile(pos.Filename), Parameters: nil, // do not show parameters in debugger @@ -129,7 +131,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri builder.SetInsertPointAtEnd(entry) if c.Debug() { - pos := c.ir.Program.Fset.Position(pos) + pos := c.program.Fset.Position(pos) diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ File: c.getDIFile(pos.Filename), Parameters: nil, // do not show parameters in debugger diff --git a/compiler/interface.go b/compiler/interface.go index fd48f261d4..72df707d77 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -11,7 +11,6 @@ import ( "strconv" "strings" - "github.com/tinygo-org/tinygo/ir" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -236,7 +235,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { return llvm.ConstGEP(global, []llvm.Value{zero, zero}) } - ms := c.ir.Program.MethodSets.MethodSet(typ) + ms := c.program.MethodSets.MethodSet(typ) if ms.Len() == 0 { // no methods, so can leave that one out return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0)) @@ -247,15 +246,23 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { for i := 0; i < ms.Len(); i++ { method := ms.At(i) signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) - f := c.ir.GetFunction(c.ir.Program.MethodValue(method)) - if f.LLVMFn.IsNil() { + fn := c.program.MethodValue(method) + llvmFn := c.getFunction(fn) + if llvmFn.IsNil() { // compiler error, so panic - panic("cannot find function: " + f.LinkName()) + panic("cannot find function: " + c.getFunctionInfo(fn).linkName) } - fn := c.getInterfaceInvokeWrapper(f) + if isAnonymous(typ) && llvmFn.IsDeclaration() { + // Inline types may also have methods when they embed interface + // types with methods. Example: struct{ error } + irbuilder := c.ctx.NewBuilder() + defer irbuilder.Dispose() + c.createFunction(irbuilder, fn, llvmFn) + } + wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn) methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{ signatureGlobal, - llvm.ConstPtrToInt(fn, c.uintptrType), + llvm.ConstPtrToInt(wrapper, c.uintptrType), }) methods[i] = methodInfo } @@ -303,7 +310,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value { // external *i8 indicating the indicating the signature of this method. It is // used during the interface lowering pass. func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { - signature := ir.MethodSignature(method) + signature := methodSignature(method) signatureGlobal := c.mod.NamedGlobal("func " + signature) if signatureGlobal.IsNil() { signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature) @@ -357,8 +364,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { // value. prevBlock := b.GetInsertBlock() - okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok") - nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next") + okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok") + nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next") b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.CreateCondBr(commaOk, okBlock, nextBlock) @@ -436,8 +443,8 @@ func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value // value, dereferences or unpacks it if necessary, and calls the real method. // If the method to wrap has a pointer receiver, no wrapping is necessary and // the function is returned directly. -func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { - wrapperName := f.LinkName() + "$invoke" +func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value { + wrapperName := llvmFn.Name() + "$invoke" wrapper := c.mod.NamedFunction(wrapperName) if !wrapper.IsNil() { // Wrapper already created. Return it directly. @@ -445,8 +452,11 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { } // Get the expanded receiver type. - receiverType := c.getLLVMType(f.Params[0].Type()) - expandedReceiverType, _ := expandFormalParamType(receiverType, nil) + receiverType := c.getLLVMType(fn.Params[0].Type()) + var expandedReceiverType []llvm.Type + for _, info := range expandFormalParamType(receiverType, "", nil) { + expandedReceiverType = append(expandedReceiverType, info.llvmType) + } // Does this method even need any wrapping? if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind { @@ -454,15 +464,15 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { // Casting a function signature to a different signature and calling it // with a receiver pointer bitcasted to *i8 (as done in calls on an // interface) is hopefully a safe (defined) operation. - return f.LLVMFn + return llvmFn } // create wrapper function - fnType := f.LLVMFn.Type().ElementType() + fnType := llvmFn.Type().ElementType() paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...) wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) - if f.LLVMFn.LastParam().Name() == "parentHandle" { + if llvmFn.LastParam().Name() == "parentHandle" { wrapper.LastParam().SetName("parentHandle") } @@ -478,8 +488,8 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { // add debug info if needed if c.Debug() { - pos := c.ir.Program.Fset.Position(f.Pos()) - difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line) + pos := c.program.Fset.Position(fn.Pos()) + difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } @@ -489,13 +499,72 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0] params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...) - if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { - b.CreateCall(f.LLVMFn, params, "") + if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { + b.CreateCall(llvmFn, params, "") b.CreateRetVoid() } else { - ret := b.CreateCall(f.LLVMFn, params, "ret") + ret := b.CreateCall(llvmFn, params, "ret") b.CreateRet(ret) } return wrapper } + +// isAnonymous returns true if (and only if) this is an anonymous type: one that +// is created inline. It can have methods if it embeds a type with methods. +func isAnonymous(typ types.Type) bool { + if t, ok := typ.(*types.Pointer); ok { + typ = t.Elem() + } + if _, ok := typ.(*types.Named); !ok { + return true + } + return false +} + +// methodSignature creates a readable version of a method signature (including +// the function name, excluding the receiver name). This string is used +// internally to match interfaces and to call the correct method on an +// interface. Examples: +// +// String() string +// Read([]byte) (int, error) +func methodSignature(method *types.Func) string { + return method.Name() + signature(method.Type().(*types.Signature)) +} + +// Make a readable version of a function (pointer) signature. +// Examples: +// +// () string +// (string, int) (int, error) +func signature(sig *types.Signature) string { + s := "" + if sig.Params().Len() == 0 { + s += "()" + } else { + s += "(" + for i := 0; i < sig.Params().Len(); i++ { + if i > 0 { + s += ", " + } + s += sig.Params().At(i).Type().String() + } + s += ")" + } + if sig.Results().Len() == 0 { + // keep as-is + } else if sig.Results().Len() == 1 { + s += " " + sig.Results().At(0).Type().String() + } else { + s += " (" + for i := 0; i < sig.Results().Len(); i++ { + if i > 0 { + s += ", " + } + s += sig.Results().At(i).Type().String() + } + s += ")" + } + return s +} diff --git a/compiler/interrupt.go b/compiler/interrupt.go index e4e31c0807..82585d56b7 100644 --- a/compiler/interrupt.go +++ b/compiler/interrupt.go @@ -39,7 +39,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro // Create a new global of type runtime/interrupt.handle. Globals of this // type are lowered in the interrupt lowering pass. - globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type() + globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type() globalLLVMType := b.getLLVMType(globalType) globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10) if global := b.mod.NamedGlobal(globalName); !global.IsNil() { @@ -56,7 +56,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro // Add debug info to the interrupt global. if b.Debug() { - pos := b.ir.Program.Fset.Position(instr.Pos()) + pos := b.program.Fset.Position(instr.Pos()) diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{ Name: "interrupt" + strconv.FormatInt(id.Int64(), 10), LinkageName: globalName, diff --git a/compiler/symbol.go b/compiler/symbol.go index ec7cd34092..25b27a9470 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -15,6 +15,197 @@ import ( "tinygo.org/x/go-llvm" ) +type inlineType int + +// How much to inline. +const ( + // Default behavior. The compiler decides for itself whether any given + // function will be inlined. Whether any function is inlined depends on the + // optimization level. + inlineDefault inlineType = iota + + // Inline hint, just like the C inline keyword (signalled using + // //go:inline). The compiler will be more likely to inline this function, + // but it is not a guarantee. + inlineHint + + // Don't inline, just like the GCC noinline attribute. Signalled using + // //go:noinline. + inlineNone +) + +// functionInfo contains some information about a function or method. In +// particular, it contains information obtained from pragmas. +// +// The linkName value contains a valid link name, even though //go:linkname is +// not present. +type functionInfo struct { + linkName string // go:linkname, go:export + module string // go:wasm-module + exported bool // go:export + nobounds bool // go:nobounds + inline inlineType // go:inline +} + +// getFunction returns the LLVM function for the given *ssa.Function, creating +// it if needed. It can later be filled with compilerContext.createFunction(). +func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value { + info := c.getFunctionInfo(fn) + llvmFn := c.mod.NamedFunction(info.linkName) + if !llvmFn.IsNil() { + return llvmFn + } + + var retType llvm.Type + if fn.Signature.Results() == nil { + retType = c.ctx.VoidType() + } else if fn.Signature.Results().Len() == 1 { + retType = c.getLLVMType(fn.Signature.Results().At(0).Type()) + } else { + results := make([]llvm.Type, 0, fn.Signature.Results().Len()) + for i := 0; i < fn.Signature.Results().Len(); i++ { + results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type())) + } + retType = c.ctx.StructType(results, false) + } + + var paramInfos []paramInfo + for _, param := range fn.Params { + paramType := c.getLLVMType(param.Type()) + paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type()) + paramInfos = append(paramInfos, paramFragmentInfos...) + } + + // Add an extra parameter as the function context. This context is used in + // closures and bound methods, but should be optimized away when not used. + if !info.exported { + paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0}) + paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0}) + } + + var paramTypes []llvm.Type + for _, info := range paramInfos { + paramTypes = append(paramTypes, info.llvmType) + } + + fnType := llvm.FunctionType(retType, paramTypes, false) + llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType) + + dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") + for i, info := range paramInfos { + if info.flags¶mIsDeferenceableOrNull == 0 { + continue + } + if info.llvmType.TypeKind() == llvm.PointerTypeKind { + el := info.llvmType.ElementType() + size := c.targetData.TypeAllocSize(el) + if size == 0 { + // dereferenceable_or_null(0) appears to be illegal in LLVM. + continue + } + dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size) + llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull) + } + } + + // External/exported functions may not retain pointer values. + // https://golang.org/cmd/cgo/#hdr-Passing_pointers + if info.exported { + // Set the wasm-import-module attribute if the function's module is set. + if info.module != "" { + wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", info.module) + llvmFn.AddFunctionAttr(wasmImportModuleAttr) + } + nocaptureKind := llvm.AttributeKindID("nocapture") + nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) + for i, typ := range paramTypes { + if typ.TypeKind() == llvm.PointerTypeKind { + llvmFn.AddAttributeAtIndex(i+1, nocapture) + } + } + } + return llvmFn +} + +// getFunctionInfo returns information about a function that is not directly +// present in *ssa.Function, such as the link name and whether it should be +// exported. +func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { + info := functionInfo{} + if strings.HasPrefix(f.Name(), "C.") { + // Created by CGo: such a name cannot be created by regular C code. + info.linkName = f.Name()[2:] + info.exported = true + } else { + // Pick the default linkName. + info.linkName = f.RelString(nil) + // Check for //go: pragmas, which may change the link name (among + // others). + info.parsePragmas(f) + } + return info +} + +// parsePragmas is used by getFunctionInfo to parse function pragmas such as +// //export or //go:noinline. +func (info *functionInfo) parsePragmas(f *ssa.Function) { + // Parse compiler directives in the preceding comments. + if f.Syntax() == nil { + return + } + if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { + for _, comment := range decl.Doc.List { + text := comment.Text + if strings.HasPrefix(text, "//export ") { + // Rewrite '//export' to '//go:export' for compatibility with + // gc. + text = "//go:" + text[2:] + } + if !strings.HasPrefix(text, "//go:") { + continue + } + parts := strings.Fields(text) + switch parts[0] { + case "//go:export": + if len(parts) != 2 { + continue + } + info.linkName = parts[1] + info.exported = true + case "//go:wasm-module": + // Alternative comment for setting the import module. + if len(parts) != 2 { + continue + } + info.module = parts[1] + case "//go:inline": + info.inline = inlineHint + case "//go:noinline": + info.inline = inlineNone + case "//go:linkname": + if len(parts) != 3 || parts[1] != f.Name() { + continue + } + // Only enable go:linkname when the package imports "unsafe". + // This is a slightly looser requirement than what gc uses: gc + // requires the file to import "unsafe", not the package as a + // whole. + if hasUnsafeImport(f.Pkg.Pkg) { + info.linkName = parts[2] + } + case "//go:nobounds": + // Skip bounds checking in this function. Useful for some + // runtime functions. + // This is somewhat dangerous and thus only imported in packages + // that import unsafe. + if hasUnsafeImport(f.Pkg.Pkg) { + info.nobounds = true + } + } + } + } +} + // 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). @@ -86,7 +277,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value { // Add debug info. // TODO: this should be done for every global in the program, not just // the ones that are referenced from some code. - pos := c.ir.Program.Fset.Position(g.Pos()) + pos := c.program.Fset.Position(g.Pos()) diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{ Name: g.RelString(nil), LinkageName: info.linkName, @@ -145,3 +336,23 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) { } } } + +// Get all methods of a type. +func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection { + ms := prog.MethodSets.MethodSet(typ) + methods := make([]*types.Selection, ms.Len()) + for i := 0; i < ms.Len(); i++ { + methods[i] = ms.At(i) + } + return methods +} + +// Return true if this package imports "unsafe", false otherwise. +func hasUnsafeImport(pkg *types.Package) bool { + for _, imp := range pkg.Imports() { + if imp == types.Unsafe { + return true + } + } + return false +} diff --git a/compiler/testdata/basic.go b/compiler/testdata/basic.go new file mode 100644 index 0000000000..bcd5a28eda --- /dev/null +++ b/compiler/testdata/basic.go @@ -0,0 +1,5 @@ +package main + +func add(x, y int) int { + return x + y +} diff --git a/compiler/testdata/basic.ll b/compiler/testdata/basic.ll new file mode 100644 index 0000000000..c9c9b9e270 --- /dev/null +++ b/compiler/testdata/basic.ll @@ -0,0 +1,13 @@ +target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" +target triple = "armv7m-none-eabi" + +define internal i32 @main.add(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = add i32 %x, %y + ret i32 %0 +} + +define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret void +} diff --git a/ir/ir.go b/ir/ir.go deleted file mode 100644 index 7506400dfa..0000000000 --- a/ir/ir.go +++ /dev/null @@ -1,330 +0,0 @@ -package ir - -import ( - "go/ast" - "go/types" - "sort" - "strings" - - "github.com/tinygo-org/tinygo/loader" - "golang.org/x/tools/go/ssa" - "tinygo.org/x/go-llvm" -) - -// This file provides a wrapper around go/ssa values and adds extra -// functionality to them. - -// View on all functions, types, and globals in a program, with analysis -// results. -type Program struct { - Program *ssa.Program - LoaderProgram *loader.Program - mainPkg *ssa.Package - Functions []*Function - functionMap map[*ssa.Function]*Function -} - -// Function or method. -type Function struct { - *ssa.Function - LLVMFn llvm.Value - module string // go:wasm-module - linkName string // go:linkname, go:export - exported bool // go:export - nobounds bool // go:nobounds - flag bool // used by dead code elimination - inline InlineType // go:inline -} - -// Interface type that is at some point used in a type assert (to check whether -// it implements another interface). -type Interface struct { - Num int - Type *types.Interface -} - -type InlineType int - -// How much to inline. -const ( - // Default behavior. The compiler decides for itself whether any given - // function will be inlined. Whether any function is inlined depends on the - // optimization level. - InlineDefault InlineType = iota - - // Inline hint, just like the C inline keyword (signalled using - // //go:inline). The compiler will be more likely to inline this function, - // but it is not a guarantee. - InlineHint - - // Don't inline, just like the GCC noinline attribute. Signalled using - // //go:noinline. - InlineNone -) - -// Create and initialize a new *Program from a *ssa.Program. -func NewProgram(lprogram *loader.Program, mainPath string) *Program { - program := lprogram.LoadSSA() - program.Build() - - // Find the main package, which is a bit difficult when running a .go file - // directly. - mainPkg := program.ImportedPackage(mainPath) - if mainPkg == nil { - for _, pkgInfo := range program.AllPackages() { - if pkgInfo.Pkg.Name() == "main" { - if mainPkg != nil { - panic("more than one main package found") - } - mainPkg = pkgInfo - } - } - } - if mainPkg == nil { - panic("could not find main package") - } - - // Make a list of packages in import order. - packageList := []*ssa.Package{} - packageSet := map[string]struct{}{} - worklist := []string{"runtime", mainPath} - for len(worklist) != 0 { - pkgPath := worklist[0] - var pkg *ssa.Package - if pkgPath == mainPath { - pkg = mainPkg // necessary for compiling individual .go files - } else { - pkg = program.ImportedPackage(pkgPath) - } - if pkg == nil { - // Non-SSA package (e.g. cgo). - packageSet[pkgPath] = struct{}{} - worklist = worklist[1:] - continue - } - if _, ok := packageSet[pkgPath]; ok { - // Package already in the final package list. - worklist = worklist[1:] - continue - } - - unsatisfiedImports := make([]string, 0) - imports := pkg.Pkg.Imports() - for _, pkg := range imports { - if _, ok := packageSet[pkg.Path()]; ok { - continue - } - unsatisfiedImports = append(unsatisfiedImports, pkg.Path()) - } - if len(unsatisfiedImports) == 0 { - // All dependencies of this package are satisfied, so add this - // package to the list. - packageList = append(packageList, pkg) - packageSet[pkgPath] = struct{}{} - worklist = worklist[1:] - } else { - // Prepend all dependencies to the worklist and reconsider this - // package (by not removing it from the worklist). At that point, it - // must be possible to add it to packageList. - worklist = append(unsatisfiedImports, worklist...) - } - } - - p := &Program{ - Program: program, - LoaderProgram: lprogram, - mainPkg: mainPkg, - functionMap: make(map[*ssa.Function]*Function), - } - - for _, pkg := range packageList { - p.AddPackage(pkg) - } - - return p -} - -// Add a package to this Program. All packages need to be added first before any -// analysis is done for correct results. -func (p *Program) AddPackage(pkg *ssa.Package) { - memberNames := make([]string, 0) - for name := range pkg.Members { - memberNames = append(memberNames, name) - } - sort.Strings(memberNames) - - for _, name := range memberNames { - member := pkg.Members[name] - switch member := member.(type) { - case *ssa.Function: - p.addFunction(member) - case *ssa.Type: - methods := getAllMethods(pkg.Prog, member.Type()) - if !types.IsInterface(member.Type()) { - // named type - for _, method := range methods { - p.addFunction(pkg.Prog.MethodValue(method)) - } - } - case *ssa.Global: - // Ignore. Globals are not handled here. - case *ssa.NamedConst: - // Ignore: these are already resolved. - default: - panic("unknown member type: " + member.String()) - } - } -} - -func (p *Program) addFunction(ssaFn *ssa.Function) { - if _, ok := p.functionMap[ssaFn]; ok { - return - } - f := &Function{Function: ssaFn} - f.parsePragmas() - p.Functions = append(p.Functions, f) - p.functionMap[ssaFn] = f - - for _, anon := range ssaFn.AnonFuncs { - p.addFunction(anon) - } -} - -// Return true if this package imports "unsafe", false otherwise. -func hasUnsafeImport(pkg *types.Package) bool { - for _, imp := range pkg.Imports() { - if imp == types.Unsafe { - return true - } - } - return false -} - -func (p *Program) GetFunction(ssaFn *ssa.Function) *Function { - return p.functionMap[ssaFn] -} - -func (p *Program) MainPkg() *ssa.Package { - return p.mainPkg -} - -// Parse compiler directives in the preceding comments. -func (f *Function) parsePragmas() { - if f.Syntax() == nil { - return - } - if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { - for _, comment := range decl.Doc.List { - text := comment.Text - if strings.HasPrefix(text, "//export ") { - // Rewrite '//export' to '//go:export' for compatibility with - // gc. - text = "//go:" + text[2:] - } - if !strings.HasPrefix(text, "//go:") { - continue - } - parts := strings.Fields(text) - switch parts[0] { - case "//go:export": - if len(parts) != 2 { - continue - } - f.linkName = parts[1] - f.exported = true - case "//go:wasm-module": - // Alternative comment for setting the import module. - if len(parts) != 2 { - continue - } - f.module = parts[1] - case "//go:inline": - f.inline = InlineHint - case "//go:noinline": - f.inline = InlineNone - case "//go:linkname": - if len(parts) != 3 || parts[1] != f.Name() { - continue - } - // Only enable go:linkname when the package imports "unsafe". - // This is a slightly looser requirement than what gc uses: gc - // requires the file to import "unsafe", not the package as a - // whole. - if hasUnsafeImport(f.Pkg.Pkg) { - f.linkName = parts[2] - } - case "//go:nobounds": - // Skip bounds checking in this function. Useful for some - // runtime functions. - // This is somewhat dangerous and thus only imported in packages - // that import unsafe. - if hasUnsafeImport(f.Pkg.Pkg) { - f.nobounds = true - } - } - } - } -} - -func (f *Function) IsNoBounds() bool { - return f.nobounds -} - -// Return true iff this function is externally visible. -func (f *Function) IsExported() bool { - return f.exported || f.CName() != "" -} - -// Return the inline directive of this function. -func (f *Function) Inline() InlineType { - return f.inline -} - -// Return the module name if not the default. -func (f *Function) Module() string { - return f.module -} - -// Return the link name for this function. -func (f *Function) LinkName() string { - if f.linkName != "" { - return f.linkName - } - if f.Signature.Recv() != nil { - // Method on a defined type (which may be a pointer). - return f.RelString(nil) - } else { - // Bare function. - if name := f.CName(); name != "" { - // Name CGo functions directly. - return name - } else { - return f.RelString(nil) - } - } -} - -// Return the name of the C function if this is a CGo wrapper. Otherwise, return -// a zero-length string. -func (f *Function) CName() string { - name := f.Name() - if strings.HasPrefix(name, "_Cfunc_") { - // emitted by `go tool cgo` - return name[len("_Cfunc_"):] - } - if strings.HasPrefix(name, "C.") { - // created by ../loader/cgo.go - return name[2:] - } - return "" -} - -// Get all methods of a type. -func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection { - ms := prog.MethodSets.MethodSet(typ) - methods := make([]*types.Selection, ms.Len()) - for i := 0; i < ms.Len(); i++ { - methods[i] = ms.At(i) - } - return methods -} diff --git a/ir/passes.go b/ir/passes.go deleted file mode 100644 index ea5ecccbe0..0000000000 --- a/ir/passes.go +++ /dev/null @@ -1,149 +0,0 @@ -package ir - -import ( - "errors" - "go/types" - - "golang.org/x/tools/go/ssa" -) - -// This file implements several optimization passes (analysis + transform) to -// optimize code in SSA form before it is compiled to LLVM IR. It is based on -// the IR defined in ir.go. - -// Make a readable version of a method signature (including the function name, -// excluding the receiver name). This string is used internally to match -// interfaces and to call the correct method on an interface. Examples: -// -// String() string -// Read([]byte) (int, error) -func MethodSignature(method *types.Func) string { - return method.Name() + signature(method.Type().(*types.Signature)) -} - -// Make a readable version of a function (pointer) signature. -// Examples: -// -// () string -// (string, int) (int, error) -func signature(sig *types.Signature) string { - s := "" - if sig.Params().Len() == 0 { - s += "()" - } else { - s += "(" - for i := 0; i < sig.Params().Len(); i++ { - if i > 0 { - s += ", " - } - s += sig.Params().At(i).Type().String() - } - s += ")" - } - if sig.Results().Len() == 0 { - // keep as-is - } else if sig.Results().Len() == 1 { - s += " " + sig.Results().At(0).Type().String() - } else { - s += " (" - for i := 0; i < sig.Results().Len(); i++ { - if i > 0 { - s += ", " - } - s += sig.Results().At(i).Type().String() - } - s += ")" - } - return s -} - -// Simple pass that removes dead code. This pass makes later analysis passes -// more useful. -func (p *Program) SimpleDCE() error { - // Unmark all functions. - for _, f := range p.Functions { - f.flag = false - } - - // Initial set of live functions. Include main.main, *.init and runtime.* - // functions. - main, ok := p.mainPkg.Members["main"].(*ssa.Function) - if !ok { - if p.mainPkg.Members["main"] == nil { - return errors.New("function main is undeclared in the main package") - } else { - return errors.New("cannot declare main - must be func") - } - } - runtimePkg := p.Program.ImportedPackage("runtime") - mathPkg := p.Program.ImportedPackage("math") - taskPkg := p.Program.ImportedPackage("internal/task") - p.GetFunction(main).flag = true - worklist := []*ssa.Function{main} - for _, f := range p.Functions { - if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) { - if f.flag { - continue - } - f.flag = true - worklist = append(worklist, f.Function) - } - } - - // Mark all called functions recursively. - for len(worklist) != 0 { - f := worklist[len(worklist)-1] - worklist = worklist[:len(worklist)-1] - for _, block := range f.Blocks { - for _, instr := range block.Instrs { - if instr, ok := instr.(*ssa.MakeInterface); ok { - for _, sel := range getAllMethods(p.Program, instr.X.Type()) { - fn := p.Program.MethodValue(sel) - callee := p.GetFunction(fn) - if callee == nil { - // TODO: why is this necessary? - p.addFunction(fn) - callee = p.GetFunction(fn) - } - if !callee.flag { - callee.flag = true - worklist = append(worklist, callee.Function) - } - } - } - for _, operand := range instr.Operands(nil) { - if operand == nil || *operand == nil { - continue - } - switch operand := (*operand).(type) { - case *ssa.Function: - f := p.GetFunction(operand) - if f == nil { - // FIXME HACK: this function should have been - // discovered already. It is not for bound methods. - p.addFunction(operand) - f = p.GetFunction(operand) - } - if !f.flag { - f.flag = true - worklist = append(worklist, operand) - } - } - } - } - } - } - - // Remove unmarked functions. - livefunctions := []*Function{} - for _, f := range p.Functions { - if f.flag { - livefunctions = append(livefunctions, f) - } else { - delete(p.functionMap, f.Function) - } - } - p.Functions = livefunctions - - return nil -} diff --git a/src/internal/task/task_coroutine.go b/src/internal/task/task_coroutine.go index a9a00c612e..7133edd61e 100644 --- a/src/internal/task/task_coroutine.go +++ b/src/internal/task/task_coroutine.go @@ -85,14 +85,9 @@ type taskHolder interface { 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() }