From 93f32bc048f269c7b2fe5a6ee010369722bd8451 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 20 Jul 2019 18:41:32 +0200 Subject: [PATCH] compiler: refactor handing of functions Previously, all functions were processed first by the ir package which scanned them all for reachability. Only the reachable functions were compiled to LLVM IR. This change removes this dead code elimination pass, and in fact refactors most of that infrastructure. This cleans up the compiler and should open the door for caching the compilation and optimization of packages. --- compiler/asserts.go | 16 +-- compiler/calls.go | 6 +- compiler/compiler.go | 218 +++++++++++++----------------- compiler/defer.go | 35 +++-- compiler/func.go | 27 +++- compiler/interface.go | 27 ++-- compiler/symbol.go | 192 ++++++++++++++++++++++++++ ir/ir.go | 304 ++++++++---------------------------------- ir/passes.go | 138 ------------------- 9 files changed, 407 insertions(+), 556 deletions(-) delete mode 100644 ir/passes.go 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 -}