From b8db79f6a66500fa44c2e724441c1dfc798f71f6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 27 Mar 2020 23:00:41 +0100 Subject: [PATCH] compiler: compile all functions/methods, remove SimpleDCE This is important because once we move to compiling packages independently, SimpleDCE can't work anymore. Instead we'll have to compile all parts of a package and cache that for later reuse. --- compiler/asserts.go | 12 +- compiler/calls.go | 11 +- compiler/compiler.go | 297 +++++++++++++--------------- compiler/defer.go | 36 ++-- compiler/func.go | 14 +- compiler/goroutine.go | 4 +- compiler/interface.go | 56 ++++-- compiler/symbol.go | 211 ++++++++++++++++++++ ir/ir.go | 291 ++++++--------------------- ir/passes.go | 149 -------------- src/internal/task/task_coroutine.go | 5 - 11 files changed, 491 insertions(+), 595 deletions(-) delete mode 100644 ir/passes.go diff --git a/compiler/asserts.go b/compiler/asserts.go index e8b53f7d4..63f4b90b4 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 efe96d52d..da92e8682 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -4,6 +4,7 @@ import ( "go/types" "strconv" + "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -34,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.ir.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 diff --git a/compiler/compiler.go b/compiler/compiler.go index e4cd8d4bd..cd7c16c3a 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -11,6 +11,7 @@ import ( "go/types" "os" "path/filepath" + "sort" "strconv" "strings" @@ -60,7 +61,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 +74,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 } @@ -236,12 +239,6 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con 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} - } - // Initialize debug information. if c.Debug() { c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{ @@ -269,50 +266,42 @@ 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.ir.Program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function)) + + // Find package initializers. + var initFuncs []llvm.Value + for _, pkg := range c.ir.Packages() { + 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 c.ir.Packages() { + 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.ir.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()) 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)}, "") @@ -722,95 +711,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 paramInfos []paramInfo - for _, param := range f.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 !f.IsExported() { - 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) - - 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, 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) - 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 { +func (c *compilerContext) attachDebugInfo(f *ssa.Function) llvm.Metadata { pos := c.ir.Program.Fset.Position(f.Syntax().Pos()) - return c.attachDebugInfoRaw(f, f.LLVMFn, "", pos.Filename, pos.Line) + 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 { @@ -823,7 +734,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, @@ -851,37 +762,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() { - if b.DumpSSA() { - fmt.Printf("\nfunc %s:\n", b.fn.Function) +func (c *compilerContext) createPackage(pkg *ssa.Package, irbuilder llvm.Builder) { + memberNames := make([]string, 0) + for name := range pkg.Members { + memberNames = append(memberNames, name) } - if !b.fn.LLVMFn.IsDeclaration() { + 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) + } + 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. @@ -890,7 +863,7 @@ 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) @@ -901,7 +874,7 @@ func (b *builder) createFunctionDefinition() { // 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 } @@ -914,7 +887,7 @@ func (b *builder) createFunctionDefinition() { llvmType := b.getLLVMType(param.Type()) fields := make([]llvm.Value, 0, 1) for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) { - param := b.fn.LLVMFn.Param(llvmParamIndex) + param := b.llvmFn.Param(llvmParamIndex) param.SetName(info.name) fields = append(fields, param) llvmParamIndex++ @@ -945,8 +918,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") @@ -1040,6 +1013,11 @@ 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 @@ -1082,7 +1060,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: @@ -1097,7 +1075,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 @@ -1145,7 +1123,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, "") @@ -1371,7 +1349,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"): @@ -1382,9 +1368,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: @@ -1398,8 +1385,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()) @@ -1434,14 +1420,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/defer.go b/compiler/defer.go index 4a2a480a8..e4c78f3a3 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/func.go b/compiler/func.go index 463350aaa..08bb31e9e 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" @@ -149,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)) @@ -164,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 bb476e7c9..ca9afbb04 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.ir.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()) } diff --git a/compiler/interface.go b/compiler/interface.go index fa08c5b0e..8b764fa36 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -247,15 +247,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.ir.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 } @@ -357,8 +365,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 +444,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,7 +453,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { } // Get the expanded receiver type. - receiverType := c.getLLVMType(f.Params[0].Type()) + receiverType := c.getLLVMType(fn.Params[0].Type()) var expandedReceiverType []llvm.Type for _, info := range expandFormalParamType(receiverType, "", nil) { expandedReceiverType = append(expandedReceiverType, info.llvmType) @@ -457,15 +465,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") } @@ -481,8 +489,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.ir.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{}) } @@ -492,13 +500,25 @@ 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 +} diff --git a/compiler/symbol.go b/compiler/symbol.go index ec7cd3409..2502d3038 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). @@ -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/ir/ir.go b/ir/ir.go index 7506400df..5f7209342 100644 --- a/ir/ir.go +++ b/ir/ir.go @@ -1,14 +1,10 @@ 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 @@ -20,48 +16,9 @@ type Program struct { Program *ssa.Program LoaderProgram *loader.Program mainPkg *ssa.Package - Functions []*Function - functionMap map[*ssa.Function]*Function + mainPath string } -// 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() @@ -84,17 +41,26 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program { panic("could not find main package") } - // Make a list of packages in import order. + return &Program{ + Program: program, + LoaderProgram: lprogram, + mainPkg: mainPkg, + mainPath: mainPath, + } +} + +// Packages returns a list of all packages, sorted by import order. +func (p *Program) Packages() []*ssa.Package { packageList := []*ssa.Package{} packageSet := map[string]struct{}{} - worklist := []string{"runtime", mainPath} + worklist := []string{"runtime", p.mainPath} for len(worklist) != 0 { pkgPath := worklist[0] var pkg *ssa.Package - if pkgPath == mainPath { - pkg = mainPkg // necessary for compiling individual .go files + if pkgPath == p.mainPath { + pkg = p.mainPkg // necessary for compiling individual .go files } else { - pkg = program.ImportedPackage(pkgPath) + pkg = p.Program.ImportedPackage(pkgPath) } if pkg == nil { // Non-SSA package (e.g. cgo). @@ -130,201 +96,56 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program { } } - 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] + return packageList } 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 - } - } - } - } +// 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)) } -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) +// 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 { - // Bare function. - if name := f.CName(); name != "" { - // Name CGo functions directly. - return name - } else { - return f.RelString(nil) + s += "(" + for i := 0; i < sig.Params().Len(); i++ { + if i > 0 { + s += ", " + } + s += sig.Params().At(i).Type().String() } + s += ")" } -} - -// 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 + 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/ir/passes.go b/ir/passes.go deleted file mode 100644 index ea5ecccbe..000000000 --- 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 a9a00c612..7133edd61 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() }