diff --git a/compiler/asserts.go b/compiler/asserts.go index b9f02e849e..2538608206 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -13,7 +13,7 @@ import ( // slice. This is required by the Go language spec: an index out of bounds must // cause a panic. func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) { - if frame.fn.IsNoBounds() { + if frame.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -32,8 +32,8 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "") } - faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds") - nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next") + faultBlock := c.ctx.AddBasicBlock(frame.llvmFn, "lookup.outofbounds") + nextBlock := c.ctx.AddBasicBlock(frame.llvmFn, "lookup.next") frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes // Now do the bounds check: index >= arrayLen @@ -57,7 +57,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu // biggest possible slice capacity, 'low' means len and 'high' means cap. The // logic is the same in both cases. func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { - if frame.fn.IsNoBounds() { + if frame.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -101,8 +101,8 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l } } - faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds") - nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next") + faultBlock := c.ctx.AddBasicBlock(frame.llvmFn, "slice.outofbounds") + nextBlock := c.ctx.AddBasicBlock(frame.llvmFn, "slice.next") frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes // Now do the bounds check: low > high || high > capacity @@ -132,8 +132,8 @@ func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string } // Check whether this is a nil pointer. - faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil") - nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next") + faultBlock := c.ctx.AddBasicBlock(frame.llvmFn, blockPrefix+".nil") + nextBlock := c.ctx.AddBasicBlock(frame.llvmFn, blockPrefix+".next") frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes // Compare against nil. diff --git a/compiler/calls.go b/compiler/calls.go index 3cb4c12c70..66916d6613 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -19,12 +19,12 @@ func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name stri if member == nil { panic("trying to call runtime." + fnName) } - fn := c.ir.GetFunction(member.(*ssa.Function)) - if !fn.IsExported() { + fn := member.(*ssa.Function) + if !c.getFunctionInfo(fn).exported { args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle } - return c.createCall(fn.LLVMFn, args, name) + return c.createCall(c.getFunction(fn), args, name) } // Create a call to the given function with the arguments possibly expanded. diff --git a/compiler/compiler.go b/compiler/compiler.go index 050e7107a3..53700a0e07 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -10,6 +10,7 @@ import ( "go/types" "os" "path/filepath" + "sort" "strconv" "strings" @@ -91,10 +92,14 @@ type Compiler struct { ir *ir.Program diagnostics []error astComments map[string]*ast.CommentGroup + functionsToCompile []*ssa.Function // list of functions to define + functionsToCompileSet map[*ssa.Function]struct{} // set of functions to define (for fast checks) } type Frame struct { - 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 @@ -104,9 +109,9 @@ type Frame struct { deferPtr llvm.Value difunc 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 } @@ -123,8 +128,9 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) { config.BuildTags = []string{config.GOOS, config.GOARCH} } c := &Compiler{ - Config: config, - difiles: make(map[string]llvm.Metadata), + Config: config, + difiles: make(map[string]llvm.Metadata), + functionsToCompileSet: make(map[*ssa.Function]struct{}), } target, err := llvm.GetTargetFromTriple(config.Triple) @@ -299,9 +305,6 @@ func (c *Compiler) Compile(mainPath string) []error { c.ir = ir.NewProgram(lprogram, mainPath) - // Run a simple dead code elimination pass. - c.ir.SimpleDCE() - // Initialize debug information. if c.Debug { c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{ @@ -313,27 +316,10 @@ func (c *Compiler) Compile(mainPath string) []error { }) } - var frames []*Frame - c.loadASTComments(lprogram) - // Declare all functions. - for _, f := range c.ir.Functions { - frames = append(frames, c.parseFuncDecl(f)) - } - - // Add definitions to declarations. - for _, frame := range frames { - if frame.fn.Synthetic == "package initializer" { - c.initFuncs = append(c.initFuncs, frame.fn.LLVMFn) - } - if frame.fn.CName() != "" { - continue - } - if frame.fn.Blocks == nil { - continue // external function - } - c.parseFunc(frame) + for _, pkg := range c.ir.Packages() { + c.parsePackage(pkg) } // Define the already declared functions that wrap methods for use in @@ -344,15 +330,16 @@ func (c *Compiler) Compile(mainPath string) []error { // 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()) c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } - block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry") + block := c.ctx.AddBasicBlock(llvmInitFn, "entry") c.builder.SetInsertPointAtEnd(block) for _, fn := range c.initFuncs { c.builder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "") @@ -739,75 +726,55 @@ func (c *Compiler) getDIType(typ types.Type) llvm.Metadata { } } -func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame { - frame := &Frame{ - fn: f, - locals: make(map[ssa.Value]llvm.Value), - blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), - blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock), - } - - 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 - for _, param := range f.Params { - paramType := c.getLLVMType(param.Type()) - paramTypeFragments := c.expandFormalParamType(paramType) - paramTypes = append(paramTypes, paramTypeFragments...) - } - - // 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 - } - - fnType := llvm.FunctionType(retType, paramTypes, false) - - name := f.LinkName() - frame.fn.LLVMFn = c.mod.NamedFunction(name) - if frame.fn.LLVMFn.IsNil() { - frame.fn.LLVMFn = llvm.AddFunction(c.mod, name, fnType) +func (c *Compiler) parsePackage(pkg *ssa.Package) { + memberNames := make([]string, 0) + for name := range pkg.Members { + memberNames = append(memberNames, name) } + sort.Strings(memberNames) - // 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()) - frame.fn.LLVMFn.AddFunctionAttr(wasmImportModuleAttr) - } - nocaptureKind := llvm.AttributeKindID("nocapture") - nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) - for i, typ := range paramTypes { - if typ.TypeKind() == llvm.PointerTypeKind { - frame.fn.LLVMFn.AddAttributeAtIndex(i+1, nocapture) + for _, name := range memberNames { + member := pkg.Members[name] + switch member := member.(type) { + case *ssa.Function: + // Parse this function. + fn := c.getFunction(member) + if member.Synthetic == "package initializer" { + c.initFuncs = append(c.initFuncs, fn) + } + c.flushFunctionWorklist() + case *ssa.Type: + if !types.IsInterface(member.Type()) { + // Named type. We should make sure all methods are parsed. + methods := getAllMethods(pkg.Prog, member.Type()) + for _, method := range methods { + // Parse this method. + c.getFunction(pkg.Prog.MethodValue(method)) + c.flushFunctionWorklist() + } } + case *ssa.Global: + c.getGlobal(member) + case *ssa.NamedConst: + // Ignore: these are already resolved. + default: + panic("unknown member type: " + member.String()) } } - - return frame +} +func (c *Compiler) flushFunctionWorklist() { + for i := 0; i < len(c.functionsToCompile); i++ { + c.parseFunc(c.functionsToCompile[i]) + } + c.functionsToCompile = c.functionsToCompile[:0] } -func (c *Compiler) attachDebugInfo(f *ir.Function) llvm.Metadata { +func (c *Compiler) 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) } -func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata { +func (c *Compiler) attachDebugInfoRaw(f *ssa.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata { if _, ok := c.difiles[filename]; !ok { dir, file := filepath.Split(filename) if dir != "" { @@ -828,7 +795,7 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, }) difunc := c.dibuilder.CreateFunction(c.difiles[filename], llvm.DIFunction{ Name: f.RelString(nil) + suffix, - LinkageName: f.LinkName() + suffix, + LinkageName: c.getFunctionInfo(f).linkName + suffix, File: c.difiles[filename], Line: line, Type: diFuncType, @@ -842,32 +809,40 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, return difunc } -func (c *Compiler) parseFunc(frame *Frame) { +func (c *Compiler) parseFunc(f *ssa.Function) { if c.DumpSSA { - fmt.Printf("\nfunc %s:\n", frame.fn.Function) + fmt.Printf("\nfunc %s:\n", f) + } + frame := &Frame{ + fn: f, + info: c.getFunctionInfo(f), + llvmFn: c.getFunction(f), + locals: make(map[ssa.Value]llvm.Value), + blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), + blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock), } - if !frame.fn.LLVMFn.IsDeclaration() { - c.addError(frame.fn.Pos(), "function is already defined:"+frame.fn.LLVMFn.Name()) + if !frame.llvmFn.IsDeclaration() { + c.addError(frame.fn.Pos(), "function is already defined:"+frame.llvmFn.Name()) return } - if !frame.fn.IsExported() { - frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) - frame.fn.LLVMFn.SetUnnamedAddr(true) + if !frame.info.exported { + frame.llvmFn.SetLinkage(llvm.InternalLinkage) + frame.llvmFn.SetUnnamedAddr(true) } - if frame.fn.IsInterrupt() && strings.HasPrefix(c.Triple, "avr") { - frame.fn.LLVMFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL + if frame.info.interrupt && strings.HasPrefix(c.Triple, "avr") { + frame.llvmFn.SetFunctionCallConv(85) // CallingConv::AVR_SIGNAL } // Some functions have a pragma controlling the inlining level. - switch frame.fn.Inline() { - case ir.InlineHint: + switch frame.info.inline { + case inlineHint: // Add LLVM inline hint to functions with //go:inline pragma. inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0) - frame.fn.LLVMFn.AddFunctionAttr(inline) - case ir.InlineNone: + frame.llvmFn.AddFunctionAttr(inline) + case inlineNone: // Add LLVM attribute to always avoid inlining this function. noinline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0) - frame.fn.LLVMFn.AddFunctionAttr(noinline) + frame.llvmFn.AddFunctionAttr(noinline) } // Add debug info, if needed. @@ -875,7 +850,7 @@ func (c *Compiler) parseFunc(frame *Frame) { if frame.fn.Synthetic == "package initializer" { // Package initializers have no debug info. Create some fake debug // info to at least have *something*. - frame.difunc = c.attachDebugInfoRaw(frame.fn, frame.fn.LLVMFn, "", "", 0) + frame.difunc = c.attachDebugInfoRaw(frame.fn, frame.llvmFn, "", "", 0) } else if frame.fn.Syntax() != nil { // Create debug info file if needed. frame.difunc = c.attachDebugInfo(frame.fn) @@ -886,7 +861,7 @@ func (c *Compiler) parseFunc(frame *Frame) { // Pre-create all basic blocks in the function. for _, block := range frame.fn.DomPreorder() { - llvmBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, block.Comment) + llvmBlock := c.ctx.AddBasicBlock(frame.llvmFn, block.Comment) frame.blockEntries[block] = llvmBlock frame.blockExits[block] = llvmBlock } @@ -899,7 +874,7 @@ func (c *Compiler) parseFunc(frame *Frame) { llvmType := c.getLLVMType(param.Type()) fields := make([]llvm.Value, 0, 1) for range c.expandFormalParamType(llvmType) { - fields = append(fields, frame.fn.LLVMFn.Param(llvmParamIndex)) + fields = append(fields, frame.llvmFn.Param(llvmParamIndex)) llvmParamIndex++ } frame.locals[param] = c.collapseFormalParam(llvmType, fields) @@ -937,8 +912,8 @@ func (c *Compiler) parseFunc(frame *Frame) { // Load free variables from the context. This is a closure (or bound // method). var context llvm.Value - if !frame.fn.IsExported() { - parentHandle := frame.fn.LLVMFn.LastParam() + if !frame.info.exported { + parentHandle := frame.llvmFn.LastParam() parentHandle.SetName("parentHandle") context = llvm.PrevParam(parentHandle) context.SetName("context") @@ -1039,16 +1014,16 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { if callee := instr.Call.StaticCallee(); callee != nil { // Static callee is known. This makes it easier to start a new // goroutine. - calleeFn := c.ir.GetFunction(callee) - if !calleeFn.IsExported() && c.selectScheduler() != "tasks" { + if !c.getFunctionInfo(callee).exported && c.selectScheduler() != "tasks" { // For coroutine scheduling, this is only required when calling // an external function. // For tasks, because all params are stored in a single object, // no unnecessary parameters should be stored anyway. params = append(params, llvm.Undef(c.i8ptrType)) // context parameter params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle + } - c.emitStartGoroutine(calleeFn.LLVMFn, params) + c.emitStartGoroutine(c.getFunction(callee), params) } else if !instr.Call.IsInvoke() { // This is a function pointer. // At the moment, two extra params are passed to the newly started @@ -1096,7 +1071,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { c.builder.CreateRet(c.getValue(frame, instr.Results[0])) } else { // Multiple return values. Put them all in a struct. - retVal := llvm.ConstNull(frame.fn.LLVMFn.Type().ElementType().ReturnType()) + retVal := llvm.ConstNull(frame.llvmFn.Type().ElementType().ReturnType()) for i, result := range instr.Results { val := c.getValue(frame, result) retVal = c.builder.CreateInsertValue(retVal, val, i, "") @@ -1334,9 +1309,9 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e return c.emitVolatileStore(frame, instr) } - targetFunc := c.ir.GetFunction(fn) - if targetFunc.LLVMFn.IsNil() { - return llvm.Value{}, c.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName()) + targetFunc := c.getFunction(fn) + if targetFunc.IsNil() { + return llvm.Value{}, c.makeError(instr.Pos(), "undefined function: "+c.getFunctionInfo(fn).linkName) } var context llvm.Value switch value := instr.Value.(type) { @@ -1351,7 +1326,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e default: panic("StaticCallee returned an unexpected value") } - return c.parseFunctionCall(frame, instr.Args, targetFunc.LLVMFn, context, targetFunc.IsExported()), nil + return c.parseFunctionCall(frame, instr.Args, targetFunc, context, c.getFunctionInfo(fn).exported), nil } // Builtin or function pointer. @@ -1373,14 +1348,13 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e func (c *Compiler) getValue(frame *Frame, expr ssa.Value) llvm.Value { switch expr := expr.(type) { case *ssa.Const: - return c.parseConst(frame.fn.LinkName(), expr) + return c.parseConst(frame.info.linkName, expr) case *ssa.Function: - fn := c.ir.GetFunction(expr) - if fn.IsExported() { + if c.getFunctionInfo(expr).exported { c.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String()) return llvm.Undef(c.getLLVMType(expr.Type())) } - return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature) + return c.createFuncValue(c.getFunction(expr), llvm.Undef(c.i8ptrType), expr.Signature) case *ssa.Global: value := c.getGlobal(expr) if value.IsNil() { diff --git a/compiler/defer.go b/compiler/defer.go index c408b6e784..3961cdf690 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -14,7 +14,6 @@ package compiler // frames. import ( - "github.com/tinygo-org/tinygo/ir" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -24,9 +23,9 @@ import ( // calls. func (c *Compiler) deferInitFunc(frame *Frame) { // Some setup. - frame.deferFuncs = make(map[*ir.Function]int) + frame.deferFuncs = make(map[*ssa.Function]int) frame.deferInvokeFuncs = make(map[string]int) - frame.deferClosureFuncs = make(map[*ir.Function]int) + frame.deferClosureFuncs = make(map[*ssa.Function]int) // Create defer list pointer. deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0) @@ -68,13 +67,11 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { } else if callee, ok := instr.Call.Value.(*ssa.Function); ok { // Regular function call. - fn := c.ir.GetFunction(callee) - - if _, ok := frame.deferFuncs[fn]; !ok { - frame.deferFuncs[fn] = len(frame.allDeferFuncs) - frame.allDeferFuncs = append(frame.allDeferFuncs, fn) + if _, ok := frame.deferFuncs[callee]; !ok { + frame.deferFuncs[callee] = len(frame.allDeferFuncs) + frame.allDeferFuncs = append(frame.allDeferFuncs, callee) } - callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferFuncs[fn]), false) + callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferFuncs[callee]), false) // Collect all values to be put in the struct (starting with // runtime._defer fields). @@ -96,7 +93,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) { context := c.builder.CreateExtractValue(closure, 0, "") // Get the callback number. - fn := c.ir.GetFunction(makeClosure.Fn.(*ssa.Function)) + fn := makeClosure.Fn.(*ssa.Function) if _, ok := frame.deferClosureFuncs[fn]; !ok { frame.deferClosureFuncs[fn] = len(frame.allDeferFuncs) frame.allDeferFuncs = append(frame.allDeferFuncs, makeClosure) @@ -157,10 +154,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) { // } // Create loop. - loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead") - loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop") - unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default") - end := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end") + loophead := llvm.AddBasicBlock(frame.llvmFn, "rundefers.loophead") + loop := llvm.AddBasicBlock(frame.llvmFn, "rundefers.loop") + unreachable := llvm.AddBasicBlock(frame.llvmFn, "rundefers.default") + end := llvm.AddBasicBlock(frame.llvmFn, "rundefers.end") c.builder.CreateBr(loophead) // Create loop head: @@ -192,7 +189,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) { // Create switch case, for example: // case 0: // // run first deferred call - block := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback") + block := llvm.AddBasicBlock(frame.llvmFn, "rundefers.callback") sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block) c.builder.SetInsertPointAtEnd(block) switch callback := callback.(type) { @@ -230,7 +227,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) { fnPtr, _ := c.getInvokeCall(frame, callback) c.createCall(fnPtr, forwardParams, "") - case *ir.Function: + case *ssa.Function: // Direct call. // Get the real defer struct type and cast to it. @@ -258,11 +255,11 @@ func (c *Compiler) emitRunDefers(frame *Frame) { forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType)) // Call real function. - c.createCall(callback.LLVMFn, forwardParams, "") + c.createCall(c.getFunction(callback), forwardParams, "") case *ssa.MakeClosure: // Get the real defer struct type and cast to it. - fn := c.ir.GetFunction(callback.Fn.(*ssa.Function)) + fn := callback.Fn.(*ssa.Function) valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)} params := fn.Signature.Params() for i := 0; i < params.Len(); i++ { @@ -285,7 +282,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) { forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType)) // Call deferred function. - c.createCall(fn.LLVMFn, forwardParams, "") + c.createCall(c.getFunction(fn), forwardParams, "") default: panic("unknown deferred function type") diff --git a/compiler/func.go b/compiler/func.go index df364b1302..9f602b07c4 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -166,13 +166,36 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type { return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace) } +// getFuncReturnType converts zero, one, or more return values in an appropriate +// LLVM type. A signature with zero return values is translated into void, one +// is returned normally, and multiple are returned as a struct. +func (c *Compiler) getFuncReturnType(typ *types.Signature) llvm.Type { + switch typ.Results().Len() { + case 0: + // No return values. + return c.ctx.VoidType() + case 1: + // Just one return value. + return c.getLLVMType(typ.Results().At(0).Type()) + default: + // Multiple return values. Put them together in a struct. + // This appears to be the common way to handle multiple return values in + // LLVM. + members := make([]llvm.Type, typ.Results().Len()) + for i := 0; i < typ.Results().Len(); i++ { + members[i] = c.getLLVMType(typ.Results().At(i).Type()) + } + return c.ctx.StructType(members, false) + } +} + // parseMakeClosure makes a function value (with context) from the given // closure expression. func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) { if len(expr.Bindings) == 0 { panic("unexpected: MakeClosure without bound variables") } - f := c.ir.GetFunction(expr.Fn.(*ssa.Function)) + f := expr.Fn.(*ssa.Function) // Collect all bound variables. boundVars := make([]llvm.Value, len(expr.Bindings)) @@ -187,5 +210,5 @@ func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.V context := c.emitPointerPack(boundVars) // Create the closure. - return c.createFuncValue(f.LLVMFn, context, f.Signature), nil + return c.createFuncValue(c.getFunction(f), context, f.Signature), nil } diff --git a/compiler/interface.go b/compiler/interface.go index 672f7a17bc..d9ba63fa9c 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -251,10 +251,10 @@ func (c *Compiler) 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() { + f := c.ir.Program.MethodValue(method) + if c.getFunction(f).IsNil() { // compiler error, so panic - panic("cannot find function: " + f.LinkName()) + panic("cannot find function: " + c.getFunctionInfo(f).linkName) } fn := c.getInterfaceInvokeWrapper(f) methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{ @@ -356,8 +356,8 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu // value. prevBlock := c.builder.GetInsertBlock() - okBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.ok") - nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.next") + okBlock := c.ctx.AddBasicBlock(frame.llvmFn, "typeassert.ok") + nextBlock := c.ctx.AddBasicBlock(frame.llvmFn, "typeassert.next") frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes c.builder.CreateCondBr(commaOk, okBlock, nextBlock) @@ -430,7 +430,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu // createInterfaceInvokeWrapper. The former is called during IR construction // itself and the latter is called when finishing up the IR. type interfaceInvokeWrapper struct { - fn *ir.Function + fn *ssa.Function wrapper llvm.Value receiverType llvm.Type } @@ -439,8 +439,8 @@ type interfaceInvokeWrapper struct { // the underlying value, dereferences it, and calls the real method. This // wrapper is only needed when the interface value actually doesn't fit in a // pointer and a pointer to the value must be created. -func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { - wrapperName := f.LinkName() + "$invoke" +func (c *Compiler) getInterfaceInvokeWrapper(f *ssa.Function) llvm.Value { + wrapperName := c.getFunctionInfo(f).linkName + "$invoke" wrapper := c.mod.NamedFunction(wrapperName) if !wrapper.IsNil() { // Wrapper already created. Return it directly. @@ -457,11 +457,11 @@ func (c *Compiler) 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 c.getFunction(f) } // create wrapper function - fnType := f.LLVMFn.Type().ElementType() + fnType := c.getFunction(f).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) @@ -495,11 +495,12 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) { receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0] params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...) - if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { - c.builder.CreateCall(fn.LLVMFn, params, "") + llvmFn := c.getFunction(fn) + if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { + c.builder.CreateCall(llvmFn, params, "") c.builder.CreateRetVoid() } else { - ret := c.builder.CreateCall(fn.LLVMFn, params, "ret") + ret := c.builder.CreateCall(llvmFn, params, "ret") c.builder.CreateRet(ret) } } diff --git a/compiler/symbol.go b/compiler/symbol.go index 4b8fb08bf6..1d763d85cc 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -14,6 +14,178 @@ 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, go:interrupt + module string // go:wasm-module + exported bool // go:export + nobounds bool // go:nobounds + flag bool // used by dead code elimination + interrupt bool // go:interrupt + inline inlineType // go:inline +} + +func (c *Compiler) getFunction(fn *ssa.Function) llvm.Value { + info := c.getFunctionInfo(fn) + llvmFn := c.mod.NamedFunction(info.linkName) + if llvmFn.IsNil() { + llvmFn = c.declareFunction(fn, info) + } + if _, ok := c.functionsToCompileSet[fn]; !ok { + if fn.Blocks != nil { + c.functionsToCompile = append(c.functionsToCompile, fn) + c.functionsToCompileSet[fn] = struct{}{} + } + } + return llvmFn +} + +func (c *Compiler) declareFunction(fn *ssa.Function, info functionInfo) llvm.Value { + var paramTypes []llvm.Type + for _, param := range fn.Params { + paramType := c.getLLVMType(param.Type()) + paramTypeFragments := c.expandFormalParamType(paramType) + paramTypes = append(paramTypes, paramTypeFragments...) + } + + // 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 { + paramTypes = append(paramTypes, c.i8ptrType) // context + paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine + } + + returnType := c.getFuncReturnType(fn.Signature) + fnType := llvm.FunctionType(returnType, paramTypes, false) + + llvmFn := llvm.AddFunction(c.mod, info.linkName, fnType) + + // 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 fnType.ParamTypes() { + if typ.TypeKind() == llvm.PointerTypeKind { + llvmFn.AddAttributeAtIndex(i+1, nocapture) + } + } + } + return llvmFn +} + +func (c *Compiler) 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 +} + +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:interrupt": + if len(parts) != 2 { + continue + } + name := parts[1] + if strings.HasSuffix(name, "_vect") { + // AVR vector naming + name = "__vector_" + name[:len(name)-5] + } + info.linkName = name + info.exported = true + info.interrupt = true + 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). @@ -105,3 +277,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 f3327a7919..d9b6610ad3 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,49 +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, go:interrupt - exported bool // go:export - nobounds bool // go:nobounds - flag bool // used by dead code elimination - interrupt bool // go:interrupt - 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() @@ -85,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). @@ -131,218 +96,55 @@ 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) { - 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:interrupt": - if len(parts) != 2 { - continue - } - name := parts[1] - if strings.HasSuffix(name, "_vect") { - // AVR vector naming - name = "__vector_" + name[:len(name)-5] - } - f.linkName = name - f.exported = true - f.interrupt = true - 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 true for functions annotated with //go:interrupt. The function name is -// already customized in LinkName() to hook up in the interrupt vector. +// 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: // -// On some platforms (like AVR), interrupts need a special compiler flag. -func (f *Function) IsInterrupt() bool { - return f.interrupt -} - -// 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 +// String() string +// Read([]byte) (int, error) +func MethodSignature(method *types.Func) string { + return method.Name() + signature(method.Type().(*types.Signature)) } -// 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) + 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 methods + return s } diff --git a/ir/passes.go b/ir/passes.go deleted file mode 100644 index 0fa7a4fdf8..0000000000 --- a/ir/passes.go +++ /dev/null @@ -1,138 +0,0 @@ -package ir - -import ( - "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() { - // Unmark all functions. - for _, f := range p.Functions { - f.flag = false - } - - // Initial set of live functions. Include main.main, *.init and runtime.* - // functions. - main := p.mainPkg.Members["main"].(*ssa.Function) - runtimePkg := p.Program.ImportedPackage("runtime") - mathPkg := p.Program.ImportedPackage("math") - 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 == 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 -}