diff --git a/Makefile b/Makefile index 565217162b..69eacadf0a 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,7 @@ endif clean: @rm -rf build -FMT_PATHS = ./*.go builder cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform +FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform fmt: @gofmt -l -w $(FMT_PATHS) fmt-check: diff --git a/compiler/asserts.go b/compiler/asserts.go index e8b53f7d46..63f4b90b41 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -16,7 +16,7 @@ import ( // slice. This is required by the Go language spec: an index out of bounds must // cause a panic. func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) { - if b.fn.IsNoBounds() { + if b.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -48,7 +48,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType // biggest possible slice capacity, 'low' means len and 'high' means cap. The // logic is the same in both cases. func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { - if b.fn.IsNoBounds() { + if b.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -104,7 +104,7 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo // createChanBoundsCheck creates a bounds check before creating a new channel to // check that the value is not too big for runtime.chanMake. func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) { - if b.fn.IsNoBounds() { + if b.info.nobounds { // The //go:nobounds pragma was added to the function to avoid bounds // checking. return @@ -189,7 +189,7 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str // createNegativeShiftCheck creates an assertion that panics if the given shift value is negative. // This function assumes that the shift value is signed. func (b *builder) createNegativeShiftCheck(shift llvm.Value) { - if b.fn.IsNoBounds() { + if b.info.nobounds { // Function disabled bounds checking - skip shift check. return } @@ -212,8 +212,8 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc } } - faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw") - nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next") + faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw") + nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next") b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes // Now branch to the out-of-bounds or the regular block. diff --git a/compiler/calls.go b/compiler/calls.go index efe96d52d0..87eab60484 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -4,6 +4,7 @@ import ( "go/types" "strconv" + "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -34,14 +35,14 @@ const ( // createCall creates a new call to runtime. with the given arguments. func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value { - fullName := "runtime." + fnName - fn := b.mod.NamedFunction(fullName) - if fn.IsNil() { - panic("trying to call non-existent function: " + fullName) + fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function) + llvmFn := b.getFunction(fn) + if llvmFn.IsNil() { + panic("trying to call non-existent function: " + fn.RelString(nil)) } args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle - return b.createCall(fn, args, name) + return b.createCall(llvmFn, args, name) } // createCall creates a call to the given function with the arguments possibly diff --git a/compiler/compiler.go b/compiler/compiler.go index c5b7be6581..745d2ad2cb 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -15,7 +15,6 @@ import ( "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler/llvmutil" - "github.com/tinygo-org/tinygo/ir" "github.com/tinygo-org/tinygo/loader" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" @@ -49,7 +48,7 @@ type compilerContext struct { i8ptrType llvm.Type // for convenience funcPtrAddrSpace int uintptrType llvm.Type - ir *ir.Program + program *ssa.Program diagnostics []error astComments map[string]*ast.CommentGroup } @@ -97,7 +96,9 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *c type builder struct { *compilerContext llvm.Builder - fn *ir.Function + fn *ssa.Function + llvmFn llvm.Value + info functionInfo locals map[ssa.Value]llvm.Value // local variables blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks @@ -108,19 +109,21 @@ type builder struct { difunc llvm.Metadata dilocals map[*types.Var]llvm.Metadata allDeferFuncs []interface{} - deferFuncs map[*ir.Function]int + deferFuncs map[*ssa.Function]int deferInvokeFuncs map[string]int - deferClosureFuncs map[*ir.Function]int + deferClosureFuncs map[*ssa.Function]int deferExprFuncs map[ssa.Value]int selectRecvBuf map[*ssa.Select]llvm.Value deferBuiltinFuncs map[ssa.Value]deferBuiltin } -func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ir.Function) *builder { +func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *builder { return &builder{ compilerContext: c, Builder: irbuilder, fn: f, + llvmFn: c.getFunction(f), + info: c.getFunctionInfo(f), locals: make(map[ssa.Value]llvm.Value), dilocals: make(map[*types.Var]llvm.Metadata), blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), @@ -214,10 +217,11 @@ func Sizes(machine llvm.TargetMachine) types.Sizes { func CompileProgram(pkgName string, lprogram *loader.Program, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []error) { c := newCompilerContext(pkgName, machine, config) - c.ir = ir.NewProgram(lprogram) + c.program = lprogram.LoadSSA() + c.program.Build() // Run a simple dead code elimination pass. - err := c.ir.SimpleDCE() + functions, err := c.simpleDCE(lprogram) if err != nil { return llvm.Module{}, []error{err} } @@ -239,7 +243,7 @@ func CompileProgram(pkgName string, lprogram *loader.Program, machine llvm.Targe // TODO: lazily create runtime types in getLLVMRuntimeType when they are // needed. Eventually this will be required anyway, when packages are // compiled independently (and the runtime types are not available). - for _, member := range c.ir.Program.ImportedPackage("runtime").Members { + for _, member := range c.program.ImportedPackage("runtime").Members { if member, ok := member.(*ssa.Type); ok { if typ, ok := member.Type().(*types.Named); ok { if _, ok := typ.Underlying().(*types.Struct); ok { @@ -249,21 +253,17 @@ func CompileProgram(pkgName string, lprogram *loader.Program, machine llvm.Targe } } - // Declare all functions. - for _, f := range c.ir.Functions { - c.createFunctionDeclaration(f) - } + // Predeclare the runtime.alloc function, which is used by the wordpack + // functionality. + c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function)) // Add definitions to declarations. var initFuncs []llvm.Value irbuilder := c.ctx.NewBuilder() defer irbuilder.Dispose() - for _, f := range c.ir.Functions { + for _, f := range functions { if f.Synthetic == "package initializer" { - initFuncs = append(initFuncs, f.LLVMFn) - } - if f.CName() != "" { - continue + initFuncs = append(initFuncs, c.getFunction(f)) } if f.Blocks == nil { continue // external function @@ -271,22 +271,23 @@ func CompileProgram(pkgName string, lprogram *loader.Program, machine llvm.Targe // Create the function definition. b := newBuilder(c, irbuilder, f) - b.createFunctionDefinition() + b.createFunction() } // After all packages are imported, add a synthetic initializer function // that calls the initializer of each package. - initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)) - initFn.LLVMFn.SetLinkage(llvm.InternalLinkage) - initFn.LLVMFn.SetUnnamedAddr(true) + initFn := c.program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function) + llvmInitFn := c.getFunction(initFn) + llvmInitFn.SetLinkage(llvm.InternalLinkage) + llvmInitFn.SetUnnamedAddr(true) if c.Debug() { difunc := c.attachDebugInfo(initFn) - pos := c.ir.Program.Fset.Position(initFn.Pos()) + pos := c.program.Fset.Position(initFn.Pos()) irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } - initFn.LLVMFn.Param(0).SetName("context") - initFn.LLVMFn.Param(1).SetName("parentHandle") - block := c.ctx.AddBasicBlock(initFn.LLVMFn, "entry") + llvmInitFn.Param(0).SetName("context") + llvmInitFn.Param(1).SetName("parentHandle") + block := c.ctx.AddBasicBlock(llvmInitFn, "entry") irbuilder.SetInsertPointAtEnd(block) for _, fn := range initFuncs { irbuilder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "") @@ -295,7 +296,7 @@ func CompileProgram(pkgName string, lprogram *loader.Program, machine llvm.Targe // Conserve for goroutine lowering. Without marking these as external, they // would be optimized away. - realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main") + realMain := c.mod.NamedFunction(lprogram.MainPkg().Pkg.Path() + ".main") realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering // Replace callMain placeholder with actual main function. @@ -377,33 +378,20 @@ func CompilePackage(moduleName string, pkg *loader.Package, machine llvm.TargetM return iPos < jPos }) - // Create *ir.Functions objects. - var functions []*ir.Function + // Define all functions. + irbuilder := c.ctx.NewBuilder() + defer irbuilder.Dispose() for _, name := range members { member := ssaPkg.Members[name] switch member := member.(type) { case *ssa.Function: - functions = append(functions, &ir.Function{ - Function: member, - }) - } - } - - // Declare all functions. - for _, fn := range functions { - c.createFunctionDeclaration(fn) - } - - // Add definitions to declarations. - irbuilder := c.ctx.NewBuilder() - defer irbuilder.Dispose() - for _, f := range functions { - if f.Blocks == nil { - continue // external function + if member.Blocks == nil { + continue // external function + } + // Create the function definition. + b := newBuilder(c, irbuilder, member) + b.createFunction() } - // Create the function definition. - b := newBuilder(c, irbuilder, f) - b.createFunctionDefinition() } return c.mod, nil @@ -602,11 +590,11 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata { Encoding: encoding, }) case *types.Chan: - return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type())) + return c.getDIType(types.NewPointer(c.program.ImportedPackage("runtime").Members["channel"].(*ssa.Type).Type())) case *types.Interface: - return c.getDIType(c.ir.Program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type()) + return c.getDIType(c.program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type()) case *types.Map: - return c.getDIType(types.NewPointer(c.ir.Program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type())) + return c.getDIType(types.NewPointer(c.program.ImportedPackage("runtime").Members["hashmap"].(*ssa.Type).Type())) case *types.Named: return c.dibuilder.CreateTypedef(llvm.DITypedef{ Type: c.getDIType(typ.Underlying()), @@ -714,7 +702,7 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata { return dilocal } - pos := b.ir.Program.Fset.Position(variable.Pos()) + pos := b.program.Fset.Position(variable.Pos()) // Check whether this is a function parameter. for i, param := range b.fn.Params { @@ -745,95 +733,17 @@ func (b *builder) getLocalVariable(variable *types.Var) llvm.Metadata { return dilocal } -// createFunctionDeclaration creates a LLVM function declaration without body. -// It can later be filled with frame.createFunctionDefinition(). -func (c *compilerContext) createFunctionDeclaration(f *ir.Function) { - var retType llvm.Type - if f.Signature.Results() == nil { - retType = c.ctx.VoidType() - } else if f.Signature.Results().Len() == 1 { - retType = c.getLLVMType(f.Signature.Results().At(0).Type()) - } else { - results := make([]llvm.Type, 0, f.Signature.Results().Len()) - for i := 0; i < f.Signature.Results().Len(); i++ { - results = append(results, c.getLLVMType(f.Signature.Results().At(i).Type())) - } - retType = c.ctx.StructType(results, false) - } - - var paramInfos []paramInfo - for _, param := range f.Params { - paramType := c.getLLVMType(param.Type()) - paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type()) - paramInfos = append(paramInfos, paramFragmentInfos...) - } - - // Add an extra parameter as the function context. This context is used in - // closures and bound methods, but should be optimized away when not used. - if !f.IsExported() { - paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0}) - paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0}) - } - - var paramTypes []llvm.Type - for _, info := range paramInfos { - paramTypes = append(paramTypes, info.llvmType) - } - - fnType := llvm.FunctionType(retType, paramTypes, false) - - name := f.LinkName() - f.LLVMFn = c.mod.NamedFunction(name) - if f.LLVMFn.IsNil() { - f.LLVMFn = llvm.AddFunction(c.mod, name, fnType) - } - - dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") - for i, info := range paramInfos { - if info.flags¶mIsDeferenceableOrNull == 0 { - continue - } - if info.llvmType.TypeKind() == llvm.PointerTypeKind { - el := info.llvmType.ElementType() - size := c.targetData.TypeAllocSize(el) - if size == 0 { - // dereferenceable_or_null(0) appears to be illegal in LLVM. - continue - } - dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size) - f.LLVMFn.AddAttributeAtIndex(i+1, dereferenceableOrNull) - } - } - - // External/exported functions may not retain pointer values. - // https://golang.org/cmd/cgo/#hdr-Passing_pointers - if f.IsExported() { - // Set the wasm-import-module attribute if the function's module is set. - if f.Module() != "" { - wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", f.Module()) - f.LLVMFn.AddFunctionAttr(wasmImportModuleAttr) - } - nocaptureKind := llvm.AttributeKindID("nocapture") - nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) - for i, typ := range paramTypes { - if typ.TypeKind() == llvm.PointerTypeKind { - f.LLVMFn.AddAttributeAtIndex(i+1, nocapture) - } - } - } -} - // attachDebugInfo adds debug info to a function declaration. It returns the // DISubprogram metadata node. -func (c *compilerContext) attachDebugInfo(f *ir.Function) llvm.Metadata { - pos := c.ir.Program.Fset.Position(f.Syntax().Pos()) - return c.attachDebugInfoRaw(f, f.LLVMFn, "", pos.Filename, pos.Line) +func (c *compilerContext) attachDebugInfo(f *ssa.Function) llvm.Metadata { + pos := c.program.Fset.Position(f.Syntax().Pos()) + return c.attachDebugInfoRaw(f, c.getFunction(f), "", pos.Filename, pos.Line) } // attachDebugInfo adds debug info to a function declaration. It returns the // DISubprogram metadata node. This method allows some more control over how // debug info is added to the function. -func (c *compilerContext) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata { +func (c *compilerContext) attachDebugInfoRaw(f *ssa.Function, llvmFn llvm.Value, suffix, filename string, line int) llvm.Metadata { // Debug info for this function. diparams := make([]llvm.Metadata, 0, len(f.Params)) for _, param := range f.Params { @@ -846,7 +756,7 @@ func (c *compilerContext) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, }) difunc := c.dibuilder.CreateFunction(c.getDIFile(filename), llvm.DIFunction{ Name: f.RelString(nil) + suffix, - LinkageName: f.LinkName() + suffix, + LinkageName: c.getFunctionInfo(f).linkName + suffix, File: c.getDIFile(filename), Line: line, Type: diFuncType, @@ -874,37 +784,37 @@ func (c *compilerContext) getDIFile(filename string) llvm.Metadata { return c.difiles[filename] } -// createFunctionDefinition builds the LLVM IR implementation for this function. -// The function must be declared but not yet defined, otherwise this function -// will create a diagnostic. -func (b *builder) createFunctionDefinition() { +// createFunction builds the LLVM IR implementation for this function. The +// function must not yet be defined, otherwise this function will create a +// diagnostic. +func (b *builder) createFunction() { if b.DumpSSA() { - fmt.Printf("\nfunc %s:\n", b.fn.Function) + fmt.Printf("\nfunc %s:\n", b.fn) } - if !b.fn.LLVMFn.IsDeclaration() { + if !b.llvmFn.IsDeclaration() { errValue := b.fn.Name() + " redeclared in this program" - fnPos := getPosition(b.fn.LLVMFn) + fnPos := getPosition(b.llvmFn) if fnPos.IsValid() { errValue += "\n\tprevious declaration at " + fnPos.String() } b.addError(b.fn.Pos(), errValue) return } - if !b.fn.IsExported() { - b.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) - b.fn.LLVMFn.SetUnnamedAddr(true) + if !b.info.exported { + b.llvmFn.SetLinkage(llvm.InternalLinkage) + b.llvmFn.SetUnnamedAddr(true) } // Some functions have a pragma controlling the inlining level. - switch b.fn.Inline() { - case ir.InlineHint: + switch b.info.inline { + case inlineHint: // Add LLVM inline hint to functions with //go:inline pragma. inline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0) - b.fn.LLVMFn.AddFunctionAttr(inline) - case ir.InlineNone: + b.llvmFn.AddFunctionAttr(inline) + case inlineNone: // Add LLVM attribute to always avoid inlining this function. noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0) - b.fn.LLVMFn.AddFunctionAttr(noinline) + b.llvmFn.AddFunctionAttr(noinline) } // Add debug info, if needed. @@ -913,18 +823,18 @@ func (b *builder) createFunctionDefinition() { // Package initializers have no debug info. Create some fake debug // info to at least have *something*. filename := b.fn.Package().Pkg.Path() + "/" - b.difunc = b.attachDebugInfoRaw(b.fn, b.fn.LLVMFn, "", filename, 0) + b.difunc = b.attachDebugInfoRaw(b.fn, b.llvmFn, "", filename, 0) } else if b.fn.Syntax() != nil { // Create debug info file if needed. b.difunc = b.attachDebugInfo(b.fn) } - pos := b.ir.Program.Fset.Position(b.fn.Pos()) + pos := b.program.Fset.Position(b.fn.Pos()) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } // Pre-create all basic blocks in the function. for _, block := range b.fn.DomPreorder() { - llvmBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, block.Comment) + llvmBlock := b.ctx.AddBasicBlock(b.llvmFn, block.Comment) b.blockEntries[block] = llvmBlock b.blockExits[block] = llvmBlock } @@ -937,7 +847,7 @@ func (b *builder) createFunctionDefinition() { llvmType := b.getLLVMType(param.Type()) fields := make([]llvm.Value, 0, 1) for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) { - param := b.fn.LLVMFn.Param(llvmParamIndex) + param := b.llvmFn.Param(llvmParamIndex) param.SetName(info.name) fields = append(fields, param) llvmParamIndex++ @@ -968,8 +878,8 @@ func (b *builder) createFunctionDefinition() { // Load free variables from the context. This is a closure (or bound // method). var context llvm.Value - if !b.fn.IsExported() { - parentHandle := b.fn.LLVMFn.LastParam() + if !b.info.exported { + parentHandle := b.llvmFn.LastParam() parentHandle.SetName("parentHandle") context = llvm.PrevParam(parentHandle) context.SetName("context") @@ -1020,7 +930,7 @@ func (b *builder) createFunctionDefinition() { continue } dbgVar := b.getLocalVariable(variable) - pos := b.ir.Program.Fset.Position(instr.Pos()) + pos := b.program.Fset.Position(instr.Pos()) b.dibuilder.InsertValueAtEnd(b.getValue(instr.X), dbgVar, b.dibuilder.CreateExpression(nil), llvm.DebugLoc{ Line: uint(pos.Line), Col: uint(pos.Column), @@ -1069,7 +979,7 @@ func (b *builder) createFunctionDefinition() { // particular Go SSA instruction. func (b *builder) createInstruction(instr ssa.Instruction) { if b.Debug() { - pos := b.ir.Program.Fset.Position(instr.Pos()) + pos := b.program.Fset.Position(instr.Pos()) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{}) } @@ -1105,7 +1015,6 @@ func (b *builder) createInstruction(instr ssa.Instruction) { if callee := instr.Call.StaticCallee(); callee != nil { // Static callee is known. This makes it easier to start a new // goroutine. - calleeFn := b.ir.GetFunction(callee) var context llvm.Value switch value := instr.Call.Value.(type) { case *ssa.Function: @@ -1120,7 +1029,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) { panic("StaticCallee returned an unexpected value") } params = append(params, context) // context parameter - b.createGoInstruction(calleeFn.LLVMFn, params, "", callee.Pos()) + b.createGoInstruction(b.getFunction(callee), params, "", callee.Pos()) } else if !instr.Call.IsInvoke() { // This is a function pointer. // At the moment, two extra params are passed to the newly started @@ -1168,7 +1077,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) { b.CreateRet(b.getValue(instr.Results[0])) } else { // Multiple return values. Put them all in a struct. - retVal := llvm.ConstNull(b.fn.LLVMFn.Type().ElementType().ReturnType()) + retVal := llvm.ConstNull(b.llvmFn.Type().ElementType().ReturnType()) for i, result := range instr.Results { val := b.getValue(result) retVal = b.CreateInsertValue(retVal, val, i, "") @@ -1411,9 +1320,10 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return b.createInterruptGlobal(instr) } - targetFunc := b.ir.GetFunction(fn) - if targetFunc.LLVMFn.IsNil() { - return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName()) + callee = b.getFunction(fn) + info := b.getFunctionInfo(fn) + if callee.IsNil() { + return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+info.linkName) } switch value := instr.Value.(type) { case *ssa.Function: @@ -1427,8 +1337,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) default: panic("StaticCallee returned an unexpected value") } - callee = targetFunc.LLVMFn - exported = targetFunc.IsExported() + exported = info.exported } else if call, ok := instr.Value.(*ssa.Builtin); ok { // Builtin function (append, close, delete, etc.).) return b.createBuiltin(instr.Args, call.Name(), instr.Pos()) @@ -1463,14 +1372,13 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) func (b *builder) getValue(expr ssa.Value) llvm.Value { switch expr := expr.(type) { case *ssa.Const: - return b.createConst(b.fn.LinkName(), expr) + return b.createConst(b.info.linkName, expr) case *ssa.Function: - fn := b.ir.GetFunction(expr) - if fn.IsExported() { + if b.getFunctionInfo(expr).exported { b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String()) return llvm.Undef(b.getLLVMType(expr.Type())) } - return b.createFuncValue(fn.LLVMFn, llvm.Undef(b.i8ptrType), fn.Signature) + return b.createFuncValue(b.getFunction(expr), llvm.Undef(b.i8ptrType), expr.Signature) case *ssa.Global: value := b.getGlobal(expr) if value.IsNil() { @@ -2658,7 +2566,7 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) { // function pointer itself. globalName := b.getGlobalInfo(unop.X.(*ssa.Global)).linkName name := globalName[:len(globalName)-len("$funcaddr")] - fn := b.mod.NamedFunction(name) + fn := b.getFunction(b.fn.Pkg.Members["C."+name].(*ssa.Function)) if fn.IsNil() { return llvm.Value{}, b.makeError(unop.Pos(), "cgo function not found: "+name) } diff --git a/compiler/defer.go b/compiler/defer.go index db2bed3282..5ebbf7db1d 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -14,9 +14,9 @@ package compiler // frames. import ( - "github.com/tinygo-org/tinygo/compiler/llvmutil" - "github.com/tinygo-org/tinygo/ir" "go/types" + + "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -26,9 +26,9 @@ import ( // calls. func (b *builder) deferInitFunc() { // Some setup. - b.deferFuncs = make(map[*ir.Function]int) + b.deferFuncs = make(map[*ssa.Function]int) b.deferInvokeFuncs = make(map[string]int) - b.deferClosureFuncs = make(map[*ir.Function]int) + b.deferClosureFuncs = make(map[*ssa.Function]int) b.deferExprFuncs = make(map[ssa.Value]int) b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin) @@ -107,13 +107,11 @@ func (b *builder) createDefer(instr *ssa.Defer) { } else if callee, ok := instr.Call.Value.(*ssa.Function); ok { // Regular function call. - fn := b.ir.GetFunction(callee) - - if _, ok := b.deferFuncs[fn]; !ok { - b.deferFuncs[fn] = len(b.allDeferFuncs) - b.allDeferFuncs = append(b.allDeferFuncs, fn) + if _, ok := b.deferFuncs[callee]; !ok { + b.deferFuncs[callee] = len(b.allDeferFuncs) + b.allDeferFuncs = append(b.allDeferFuncs, callee) } - callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false) + callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false) // Collect all values to be put in the struct (starting with // runtime._defer fields). @@ -135,7 +133,7 @@ func (b *builder) createDefer(instr *ssa.Defer) { context := b.CreateExtractValue(closure, 0, "") // Get the callback number. - fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function)) + fn := makeClosure.Fn.(*ssa.Function) if _, ok := b.deferClosureFuncs[fn]; !ok { b.deferClosureFuncs[fn] = len(b.allDeferFuncs) b.allDeferFuncs = append(b.allDeferFuncs, makeClosure) @@ -251,10 +249,10 @@ func (b *builder) createRunDefers() { // } // Create loop. - loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead") - loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop") - unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default") - end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end") + loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead") + loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop") + unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default") + end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end") b.CreateBr(loophead) // Create loop head: @@ -286,7 +284,7 @@ func (b *builder) createRunDefers() { // Create switch case, for example: // case 0: // // run first deferred call - block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback") + block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback") sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block) b.SetInsertPointAtEnd(block) switch callback := callback.(type) { @@ -350,7 +348,7 @@ func (b *builder) createRunDefers() { b.createCall(fnPtr, forwardParams, "") - case *ir.Function: + case *ssa.Function: // Direct call. // Get the real defer struct type and cast to it. @@ -372,7 +370,7 @@ func (b *builder) createRunDefers() { // Plain TinyGo functions add some extra parameters to implement async functionality and function recievers. // These parameters should not be supplied when calling into an external C/ASM function. - if !callback.IsExported() { + if !b.getFunctionInfo(callback).exported { // Add the context parameter. We know it is ignored by the receiving // function, but we have to pass one anyway. forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) @@ -382,11 +380,11 @@ func (b *builder) createRunDefers() { } // Call real function. - b.createCall(callback.LLVMFn, forwardParams, "") + b.createCall(b.getFunction(callback), forwardParams, "") case *ssa.MakeClosure: // Get the real defer struct type and cast to it. - fn := b.ir.GetFunction(callback.Fn.(*ssa.Function)) + fn := callback.Fn.(*ssa.Function) valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} params := fn.Signature.Params() for i := 0; i < params.Len(); i++ { @@ -409,7 +407,7 @@ func (b *builder) createRunDefers() { forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) // Call deferred function. - b.createCall(fn.LLVMFn, forwardParams, "") + b.createCall(b.getFunction(fn), forwardParams, "") case *ssa.Builtin: db := b.deferBuiltinFuncs[callback] diff --git a/compiler/errors.go b/compiler/errors.go index 85de4d161e..1181322980 100644 --- a/compiler/errors.go +++ b/compiler/errors.go @@ -14,7 +14,7 @@ import ( // makeError makes it easy to create an error from a token.Pos with a message. func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error { return types.Error{ - Fset: c.ir.Program.Fset, + Fset: c.program.Fset, Pos: pos, Msg: msg, } diff --git a/compiler/func.go b/compiler/func.go index 463350aaae..7f5e4398d3 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -149,7 +149,7 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) { if len(expr.Bindings) == 0 { panic("unexpected: MakeClosure without bound variables") } - f := b.ir.GetFunction(expr.Fn.(*ssa.Function)) + f := expr.Fn.(*ssa.Function) // Collect all bound variables. boundVars := make([]llvm.Value, len(expr.Bindings)) @@ -164,5 +164,5 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) { context := b.emitPointerPack(boundVars) // Create the closure. - return b.createFuncValue(f.LLVMFn, context, f.Signature), nil + return b.createFuncValue(b.getFunction(f), context, f.Signature), nil } diff --git a/compiler/goroutine.go b/compiler/goroutine.go index 23978ccb24..2afa5b0149 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -7,6 +7,7 @@ import ( "go/token" "github.com/tinygo-org/tinygo/compiler/llvmutil" + "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -28,7 +29,8 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p // function that will be replaced with a load from a special ELF // section that contains the stack size (and is modified after // linking). - stackSize = b.createCall(b.mod.NamedFunction("internal/task.getGoroutineStackSize"), []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize") + stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function)) + stackSize = b.createCall(stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize") } else { // The stack size is fixed at compile time. By emitting it here as a // constant, it can be optimized. @@ -42,7 +44,8 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p default: panic("unreachable") } - b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "") + start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) + b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "") return llvm.Undef(funcPtr.Type().ElementType().ReturnType()) } @@ -88,7 +91,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri builder.SetInsertPointAtEnd(entry) if c.Debug() { - pos := c.ir.Program.Fset.Position(pos) + pos := c.program.Fset.Position(pos) diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ File: c.getDIFile(pos.Filename), Parameters: nil, // do not show parameters in debugger @@ -145,7 +148,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri builder.SetInsertPointAtEnd(entry) if c.Debug() { - pos := c.ir.Program.Fset.Position(pos) + pos := c.program.Fset.Position(pos) diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ File: c.getDIFile(pos.Filename), Parameters: nil, // do not show parameters in debugger diff --git a/compiler/interface.go b/compiler/interface.go index 030c67571b..24986dd143 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -11,7 +11,6 @@ import ( "strconv" "strings" - "github.com/tinygo-org/tinygo/ir" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -236,7 +235,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { return llvm.ConstGEP(global, []llvm.Value{zero, zero}) } - ms := c.ir.Program.MethodSets.MethodSet(typ) + ms := c.program.MethodSets.MethodSet(typ) if ms.Len() == 0 { // no methods, so can leave that one out return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0)) @@ -247,15 +246,16 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { for i := 0; i < ms.Len(); i++ { method := ms.At(i) signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) - f := c.ir.GetFunction(c.ir.Program.MethodValue(method)) - if f.LLVMFn.IsNil() { + fn := c.program.MethodValue(method) + llvmFn := c.getFunction(fn) + if llvmFn.IsNil() { // compiler error, so panic - panic("cannot find function: " + f.LinkName()) + panic("cannot find function: " + c.getFunctionInfo(fn).linkName) } - fn := c.getInterfaceInvokeWrapper(f) + wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn) methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{ signatureGlobal, - llvm.ConstPtrToInt(fn, c.uintptrType), + llvm.ConstPtrToInt(wrapper, c.uintptrType), }) methods[i] = methodInfo } @@ -303,7 +303,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value { // external *i8 indicating the indicating the signature of this method. It is // used during the interface lowering pass. func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { - signature := ir.MethodSignature(method) + signature := methodSignature(method) signatureGlobal := c.mod.NamedGlobal("func " + signature) if signatureGlobal.IsNil() { signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature) @@ -357,8 +357,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { // value. prevBlock := b.GetInsertBlock() - okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok") - nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next") + okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok") + nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next") b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.CreateCondBr(commaOk, okBlock, nextBlock) @@ -436,8 +436,8 @@ func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value // value, dereferences or unpacks it if necessary, and calls the real method. // If the method to wrap has a pointer receiver, no wrapping is necessary and // the function is returned directly. -func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { - wrapperName := f.LinkName() + "$invoke" +func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value { + wrapperName := llvmFn.Name() + "$invoke" wrapper := c.mod.NamedFunction(wrapperName) if !wrapper.IsNil() { // Wrapper already created. Return it directly. @@ -445,7 +445,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { } // Get the expanded receiver type. - receiverType := c.getLLVMType(f.Params[0].Type()) + receiverType := c.getLLVMType(fn.Params[0].Type()) var expandedReceiverType []llvm.Type for _, info := range expandFormalParamType(receiverType, "", nil) { expandedReceiverType = append(expandedReceiverType, info.llvmType) @@ -457,11 +457,11 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { // Casting a function signature to a different signature and calling it // with a receiver pointer bitcasted to *i8 (as done in calls on an // interface) is hopefully a safe (defined) operation. - return f.LLVMFn + return llvmFn } // create wrapper function - fnType := f.LLVMFn.Type().ElementType() + fnType := llvmFn.Type().ElementType() paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...) wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) @@ -479,8 +479,8 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { // add debug info if needed if c.Debug() { - pos := c.ir.Program.Fset.Position(f.Pos()) - difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line) + pos := c.program.Fset.Position(fn.Pos()) + difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } @@ -490,13 +490,60 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0] params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...) - if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { - b.CreateCall(f.LLVMFn, params, "") + if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { + b.CreateCall(llvmFn, params, "") b.CreateRetVoid() } else { - ret := b.CreateCall(f.LLVMFn, params, "ret") + ret := b.CreateCall(llvmFn, params, "ret") b.CreateRet(ret) } return wrapper } + +// methodSignature creates a readable version of a method signature (including +// the function name, excluding the receiver name). This string is used +// internally to match interfaces and to call the correct method on an +// interface. Examples: +// +// String() string +// Read([]byte) (int, error) +func methodSignature(method *types.Func) string { + return method.Name() + signature(method.Type().(*types.Signature)) +} + +// Make a readable version of a function (pointer) signature. +// Examples: +// +// () string +// (string, int) (int, error) +func signature(sig *types.Signature) string { + s := "" + if sig.Params().Len() == 0 { + s += "()" + } else { + s += "(" + for i := 0; i < sig.Params().Len(); i++ { + if i > 0 { + s += ", " + } + s += sig.Params().At(i).Type().String() + } + s += ")" + } + if sig.Results().Len() == 0 { + // keep as-is + } else if sig.Results().Len() == 1 { + s += " " + sig.Results().At(0).Type().String() + } else { + s += " (" + for i := 0; i < sig.Results().Len(); i++ { + if i > 0 { + s += ", " + } + s += sig.Results().At(i).Type().String() + } + s += ")" + } + return s +} diff --git a/compiler/interrupt.go b/compiler/interrupt.go index e4e31c0807..82585d56b7 100644 --- a/compiler/interrupt.go +++ b/compiler/interrupt.go @@ -39,7 +39,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro // Create a new global of type runtime/interrupt.handle. Globals of this // type are lowered in the interrupt lowering pass. - globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type() + globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type() globalLLVMType := b.getLLVMType(globalType) globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10) if global := b.mod.NamedGlobal(globalName); !global.IsNil() { @@ -56,7 +56,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro // Add debug info to the interrupt global. if b.Debug() { - pos := b.ir.Program.Fset.Position(instr.Pos()) + pos := b.program.Fset.Position(instr.Pos()) diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{ Name: "interrupt" + strconv.FormatInt(id.Int64(), 10), LinkageName: globalName, diff --git a/compiler/passes.go b/compiler/passes.go new file mode 100644 index 0000000000..092f5f0b50 --- /dev/null +++ b/compiler/passes.go @@ -0,0 +1,164 @@ +package compiler + +// This file implements a simple reachability analysis, to reduce compile time. +// This DCE pass used to be necessary for improving other passes but now it +// isn't necessary anymore. + +import ( + "errors" + "go/types" + "sort" + + "github.com/tinygo-org/tinygo/loader" + "golang.org/x/tools/go/ssa" +) + +type dceState struct { + *compilerContext + functions []*dceFunction + functionMap map[*ssa.Function]*dceFunction +} + +type dceFunction struct { + *ssa.Function + functionInfo + flag bool // used by dead code elimination +} + +func (p *dceState) addFunction(ssaFn *ssa.Function) { + if _, ok := p.functionMap[ssaFn]; ok { + return + } + f := &dceFunction{Function: ssaFn} + f.functionInfo = p.getFunctionInfo(ssaFn) + p.functions = append(p.functions, f) + p.functionMap[ssaFn] = f + + for _, anon := range ssaFn.AnonFuncs { + p.addFunction(anon) + } +} + +// simpleDCE returns a list of alive functions in the program. Compiling only +// these functions makes the compiler faster. +// +// This functionality will likely be replaced in the future with build caching. +func (c *compilerContext) simpleDCE(lprogram *loader.Program) ([]*ssa.Function, error) { + mainPkg := c.program.Package(lprogram.MainPkg().Pkg) + if mainPkg == nil { + panic("could not find main package") + } + p := &dceState{ + compilerContext: c, + functionMap: make(map[*ssa.Function]*dceFunction), + } + + for _, pkg := range lprogram.Sorted() { + pkg := c.program.Package(pkg.Pkg) + 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()) + } + } + } + + // Initial set of live functions. Include main.main, *.init and runtime.* + // functions. + main, ok := mainPkg.Members["main"].(*ssa.Function) + if !ok { + if mainPkg.Members["main"] == nil { + return nil, errors.New("function main is undeclared in the main package") + } else { + return nil, errors.New("cannot declare main - must be func") + } + } + runtimePkg := c.program.ImportedPackage("runtime") + mathPkg := c.program.ImportedPackage("math") + taskPkg := c.program.ImportedPackage("internal/task") + p.functionMap[main].flag = true + worklist := []*ssa.Function{main} + for _, f := range p.functions { + if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) { + if f.flag { + continue + } + f.flag = true + worklist = append(worklist, f.Function) + } + } + + // Mark all called functions recursively. + for len(worklist) != 0 { + f := worklist[len(worklist)-1] + worklist = worklist[:len(worklist)-1] + for _, block := range f.Blocks { + for _, instr := range block.Instrs { + if instr, ok := instr.(*ssa.MakeInterface); ok { + for _, sel := range getAllMethods(c.program, instr.X.Type()) { + fn := c.program.MethodValue(sel) + callee := p.functionMap[fn] + if callee == nil { + // TODO: why is this necessary? + p.addFunction(fn) + callee = p.functionMap[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.functionMap[operand] + if f == nil { + // FIXME HACK: this function should have been + // discovered already. It is not for bound methods. + p.addFunction(operand) + f = p.functionMap[operand] + } + if !f.flag { + f.flag = true + worklist = append(worklist, operand) + } + } + } + } + } + } + + // Return all live functions. + liveFunctions := []*ssa.Function{} + for _, f := range p.functions { + if f.flag { + liveFunctions = append(liveFunctions, f.Function) + } + } + return liveFunctions, nil +} diff --git a/compiler/symbol.go b/compiler/symbol.go index ec7cd34092..89525f4933 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -15,6 +15,197 @@ import ( "tinygo.org/x/go-llvm" ) +// 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 if //go:linkname is not +// present. +type functionInfo struct { + module string // go:wasm-module + linkName string // go:linkname, go:export + exported bool // go:export, CGo + nobounds bool // go:nobounds + inline inlineType // go:inline +} + +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 +) + +// getFunction returns the LLVM function for the given *ssa.Function, creating +// it if needed. It can later be filled with compilerContext.createFunction(). +func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value { + info := c.getFunctionInfo(fn) + llvmFn := c.mod.NamedFunction(info.linkName) + if !llvmFn.IsNil() { + return llvmFn + } + + var retType llvm.Type + if fn.Signature.Results() == nil { + retType = c.ctx.VoidType() + } else if fn.Signature.Results().Len() == 1 { + retType = c.getLLVMType(fn.Signature.Results().At(0).Type()) + } else { + results := make([]llvm.Type, 0, fn.Signature.Results().Len()) + for i := 0; i < fn.Signature.Results().Len(); i++ { + results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type())) + } + retType = c.ctx.StructType(results, false) + } + + var paramInfos []paramInfo + for _, param := range fn.Params { + paramType := c.getLLVMType(param.Type()) + paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type()) + paramInfos = append(paramInfos, paramFragmentInfos...) + } + + // Add an extra parameter as the function context. This context is used in + // closures and bound methods, but should be optimized away when not used. + if !info.exported { + paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0}) + paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0}) + } + + var paramTypes []llvm.Type + for _, info := range paramInfos { + paramTypes = append(paramTypes, info.llvmType) + } + + fnType := llvm.FunctionType(retType, paramTypes, false) + llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType) + + dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") + for i, info := range paramInfos { + if info.flags¶mIsDeferenceableOrNull == 0 { + continue + } + if info.llvmType.TypeKind() == llvm.PointerTypeKind { + el := info.llvmType.ElementType() + size := c.targetData.TypeAllocSize(el) + if size == 0 { + // dereferenceable_or_null(0) appears to be illegal in LLVM. + continue + } + dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size) + llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull) + } + } + + // External/exported functions may not retain pointer values. + // https://golang.org/cmd/cgo/#hdr-Passing_pointers + if info.exported { + // Set the wasm-import-module attribute if the function's module is set. + if info.module != "" { + wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", info.module) + llvmFn.AddFunctionAttr(wasmImportModuleAttr) + } + nocaptureKind := llvm.AttributeKindID("nocapture") + nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) + for i, typ := range paramTypes { + if typ.TypeKind() == llvm.PointerTypeKind { + llvmFn.AddAttributeAtIndex(i+1, nocapture) + } + } + } + + return llvmFn +} + +// getFunctionInfo returns information about a function that is not directly +// present in *ssa.Function, such as the link name and whether it should be +// exported. +func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { + info := functionInfo{} + if strings.HasPrefix(f.Name(), "C.") { + // Created by CGo: such a name cannot be created by regular C code. + info.linkName = f.Name()[2:] + info.exported = true + } else { + // Pick the default linkName. + info.linkName = f.RelString(nil) + // Check for //go: pragmas, which may change the link name (among + // others). + info.parsePragmas(f) + } + return info +} + +// parsePragmas is used by getFunctionInfo to parse function pragmas such as +// //export or //go:noinline. +func (info *functionInfo) parsePragmas(f *ssa.Function) { + if f.Syntax() == nil { + return + } + if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { + for _, comment := range decl.Doc.List { + text := comment.Text + if strings.HasPrefix(text, "//export ") { + // Rewrite '//export' to '//go:export' for compatibility with + // gc. + text = "//go:" + text[2:] + } + if !strings.HasPrefix(text, "//go:") { + continue + } + parts := strings.Fields(text) + switch parts[0] { + case "//go:export": + if len(parts) != 2 { + continue + } + info.linkName = parts[1] + info.exported = true + case "//go:wasm-module": + // Alternative comment for setting the import module. + if len(parts) != 2 { + continue + } + info.module = parts[1] + case "//go:inline": + info.inline = inlineHint + case "//go:noinline": + info.inline = inlineNone + case "//go:linkname": + if len(parts) != 3 || parts[1] != f.Name() { + continue + } + // Only enable go:linkname when the package imports "unsafe". + // This is a slightly looser requirement than what gc uses: gc + // requires the file to import "unsafe", not the package as a + // whole. + if hasUnsafeImport(f.Pkg.Pkg) { + info.linkName = parts[2] + } + case "//go:nobounds": + // Skip bounds checking in this function. Useful for some + // runtime functions. + // This is somewhat dangerous and thus only imported in packages + // that import unsafe. + if hasUnsafeImport(f.Pkg.Pkg) { + info.nobounds = true + } + } + } + } +} + // globalInfo contains some information about a specific global. By default, // linkName is equal to .RelString(nil) on a global and extern is false, but for // some symbols this is different (due to //go:extern for example). @@ -86,7 +277,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value { // Add debug info. // TODO: this should be done for every global in the program, not just // the ones that are referenced from some code. - pos := c.ir.Program.Fset.Position(g.Pos()) + pos := c.program.Fset.Position(g.Pos()) diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{ Name: g.RelString(nil), LinkageName: info.linkName, @@ -145,3 +336,23 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) { } } } + +// Get all methods of a type. +func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection { + ms := prog.MethodSets.MethodSet(typ) + methods := make([]*types.Selection, ms.Len()) + for i := 0; i < ms.Len(); i++ { + methods[i] = ms.At(i) + } + return methods +} + +// Return true if this package imports "unsafe", false otherwise. +func hasUnsafeImport(pkg *types.Package) bool { + for _, imp := range pkg.Imports() { + if imp == types.Unsafe { + return true + } + } + return false +} diff --git a/ir/ir.go b/ir/ir.go deleted file mode 100644 index abbb403c32..0000000000 --- a/ir/ir.go +++ /dev/null @@ -1,271 +0,0 @@ -package ir - -import ( - "go/ast" - "go/types" - "sort" - "strings" - - "github.com/tinygo-org/tinygo/loader" - "golang.org/x/tools/go/ssa" - "tinygo.org/x/go-llvm" -) - -// This file provides a wrapper around go/ssa values and adds extra -// functionality to them. - -// View on all functions, types, and globals in a program, with analysis -// results. -type Program struct { - Program *ssa.Program - LoaderProgram *loader.Program - mainPkg *ssa.Package - Functions []*Function - functionMap map[*ssa.Function]*Function -} - -// Function or method. -type Function struct { - *ssa.Function - LLVMFn llvm.Value - module string // go:wasm-module - linkName string // go:linkname, go:export - exported bool // go:export - nobounds bool // go:nobounds - flag bool // used by dead code elimination - inline InlineType // go:inline -} - -// Interface type that is at some point used in a type assert (to check whether -// it implements another interface). -type Interface struct { - Num int - Type *types.Interface -} - -type InlineType int - -// How much to inline. -const ( - // Default behavior. The compiler decides for itself whether any given - // function will be inlined. Whether any function is inlined depends on the - // optimization level. - InlineDefault InlineType = iota - - // Inline hint, just like the C inline keyword (signalled using - // //go:inline). The compiler will be more likely to inline this function, - // but it is not a guarantee. - InlineHint - - // Don't inline, just like the GCC noinline attribute. Signalled using - // //go:noinline. - InlineNone -) - -// Create and initialize a new *Program from a *ssa.Program. -func NewProgram(lprogram *loader.Program) *Program { - program := lprogram.LoadSSA() - program.Build() - - mainPkg := program.Package(lprogram.MainPkg().Pkg) - if mainPkg == nil { - panic("could not find main package") - } - p := &Program{ - Program: program, - LoaderProgram: lprogram, - mainPkg: mainPkg, - functionMap: make(map[*ssa.Function]*Function), - } - - for _, pkg := range lprogram.Sorted() { - p.AddPackage(program.Package(pkg.Pkg)) - } - - return p -} - -// Add a package to this Program. All packages need to be added first before any -// analysis is done for correct results. -func (p *Program) AddPackage(pkg *ssa.Package) { - memberNames := make([]string, 0) - for name := range pkg.Members { - memberNames = append(memberNames, name) - } - sort.Strings(memberNames) - - for _, name := range memberNames { - member := pkg.Members[name] - switch member := member.(type) { - case *ssa.Function: - p.addFunction(member) - case *ssa.Type: - methods := getAllMethods(pkg.Prog, member.Type()) - if !types.IsInterface(member.Type()) { - // named type - for _, method := range methods { - p.addFunction(pkg.Prog.MethodValue(method)) - } - } - case *ssa.Global: - // Ignore. Globals are not handled here. - case *ssa.NamedConst: - // Ignore: these are already resolved. - default: - panic("unknown member type: " + member.String()) - } - } -} - -func (p *Program) addFunction(ssaFn *ssa.Function) { - if _, ok := p.functionMap[ssaFn]; ok { - return - } - f := &Function{Function: ssaFn} - f.parsePragmas() - p.Functions = append(p.Functions, f) - p.functionMap[ssaFn] = f - - for _, anon := range ssaFn.AnonFuncs { - p.addFunction(anon) - } -} - -// Return true if this package imports "unsafe", false otherwise. -func hasUnsafeImport(pkg *types.Package) bool { - for _, imp := range pkg.Imports() { - if imp == types.Unsafe { - return true - } - } - return false -} - -func (p *Program) GetFunction(ssaFn *ssa.Function) *Function { - return p.functionMap[ssaFn] -} - -func (p *Program) MainPkg() *ssa.Package { - return p.mainPkg -} - -// Parse compiler directives in the preceding comments. -func (f *Function) parsePragmas() { - if f.Syntax() == nil { - return - } - if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { - for _, comment := range decl.Doc.List { - text := comment.Text - if strings.HasPrefix(text, "//export ") { - // Rewrite '//export' to '//go:export' for compatibility with - // gc. - text = "//go:" + text[2:] - } - if !strings.HasPrefix(text, "//go:") { - continue - } - parts := strings.Fields(text) - switch parts[0] { - case "//go:export": - if len(parts) != 2 { - continue - } - f.linkName = parts[1] - f.exported = true - case "//go:wasm-module": - // Alternative comment for setting the import module. - if len(parts) != 2 { - continue - } - f.module = parts[1] - case "//go:inline": - f.inline = InlineHint - case "//go:noinline": - f.inline = InlineNone - case "//go:linkname": - if len(parts) != 3 || parts[1] != f.Name() { - continue - } - // Only enable go:linkname when the package imports "unsafe". - // This is a slightly looser requirement than what gc uses: gc - // requires the file to import "unsafe", not the package as a - // whole. - if hasUnsafeImport(f.Pkg.Pkg) { - f.linkName = parts[2] - } - case "//go:nobounds": - // Skip bounds checking in this function. Useful for some - // runtime functions. - // This is somewhat dangerous and thus only imported in packages - // that import unsafe. - if hasUnsafeImport(f.Pkg.Pkg) { - f.nobounds = true - } - } - } - } -} - -func (f *Function) IsNoBounds() bool { - return f.nobounds -} - -// Return true iff this function is externally visible. -func (f *Function) IsExported() bool { - return f.exported || f.CName() != "" -} - -// Return the inline directive of this function. -func (f *Function) Inline() InlineType { - return f.inline -} - -// Return the module name if not the default. -func (f *Function) Module() string { - return f.module -} - -// Return the link name for this function. -func (f *Function) LinkName() string { - if f.linkName != "" { - return f.linkName - } - if f.Signature.Recv() != nil { - // Method on a defined type (which may be a pointer). - return f.RelString(nil) - } else { - // Bare function. - if name := f.CName(); name != "" { - // Name CGo functions directly. - return name - } else { - return f.RelString(nil) - } - } -} - -// Return the name of the C function if this is a CGo wrapper. Otherwise, return -// a zero-length string. -func (f *Function) CName() string { - name := f.Name() - if strings.HasPrefix(name, "_Cfunc_") { - // emitted by `go tool cgo` - return name[len("_Cfunc_"):] - } - if strings.HasPrefix(name, "C.") { - // created by ../loader/cgo.go - return name[2:] - } - return "" -} - -// Get all methods of a type. -func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection { - ms := prog.MethodSets.MethodSet(typ) - methods := make([]*types.Selection, ms.Len()) - for i := 0; i < ms.Len(); i++ { - methods[i] = ms.At(i) - } - return methods -} diff --git a/ir/passes.go b/ir/passes.go deleted file mode 100644 index ea5ecccbe0..0000000000 --- a/ir/passes.go +++ /dev/null @@ -1,149 +0,0 @@ -package ir - -import ( - "errors" - "go/types" - - "golang.org/x/tools/go/ssa" -) - -// This file implements several optimization passes (analysis + transform) to -// optimize code in SSA form before it is compiled to LLVM IR. It is based on -// the IR defined in ir.go. - -// Make a readable version of a method signature (including the function name, -// excluding the receiver name). This string is used internally to match -// interfaces and to call the correct method on an interface. Examples: -// -// String() string -// Read([]byte) (int, error) -func MethodSignature(method *types.Func) string { - return method.Name() + signature(method.Type().(*types.Signature)) -} - -// Make a readable version of a function (pointer) signature. -// Examples: -// -// () string -// (string, int) (int, error) -func signature(sig *types.Signature) string { - s := "" - if sig.Params().Len() == 0 { - s += "()" - } else { - s += "(" - for i := 0; i < sig.Params().Len(); i++ { - if i > 0 { - s += ", " - } - s += sig.Params().At(i).Type().String() - } - s += ")" - } - if sig.Results().Len() == 0 { - // keep as-is - } else if sig.Results().Len() == 1 { - s += " " + sig.Results().At(0).Type().String() - } else { - s += " (" - for i := 0; i < sig.Results().Len(); i++ { - if i > 0 { - s += ", " - } - s += sig.Results().At(i).Type().String() - } - s += ")" - } - return s -} - -// Simple pass that removes dead code. This pass makes later analysis passes -// more useful. -func (p *Program) SimpleDCE() error { - // Unmark all functions. - for _, f := range p.Functions { - f.flag = false - } - - // Initial set of live functions. Include main.main, *.init and runtime.* - // functions. - main, ok := p.mainPkg.Members["main"].(*ssa.Function) - if !ok { - if p.mainPkg.Members["main"] == nil { - return errors.New("function main is undeclared in the main package") - } else { - return errors.New("cannot declare main - must be func") - } - } - runtimePkg := p.Program.ImportedPackage("runtime") - mathPkg := p.Program.ImportedPackage("math") - taskPkg := p.Program.ImportedPackage("internal/task") - p.GetFunction(main).flag = true - worklist := []*ssa.Function{main} - for _, f := range p.Functions { - if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) { - if f.flag { - continue - } - f.flag = true - worklist = append(worklist, f.Function) - } - } - - // Mark all called functions recursively. - for len(worklist) != 0 { - f := worklist[len(worklist)-1] - worklist = worklist[:len(worklist)-1] - for _, block := range f.Blocks { - for _, instr := range block.Instrs { - if instr, ok := instr.(*ssa.MakeInterface); ok { - for _, sel := range getAllMethods(p.Program, instr.X.Type()) { - fn := p.Program.MethodValue(sel) - callee := p.GetFunction(fn) - if callee == nil { - // TODO: why is this necessary? - p.addFunction(fn) - callee = p.GetFunction(fn) - } - if !callee.flag { - callee.flag = true - worklist = append(worklist, callee.Function) - } - } - } - for _, operand := range instr.Operands(nil) { - if operand == nil || *operand == nil { - continue - } - switch operand := (*operand).(type) { - case *ssa.Function: - f := p.GetFunction(operand) - if f == nil { - // FIXME HACK: this function should have been - // discovered already. It is not for bound methods. - p.addFunction(operand) - f = p.GetFunction(operand) - } - if !f.flag { - f.flag = true - worklist = append(worklist, operand) - } - } - } - } - } - } - - // Remove unmarked functions. - livefunctions := []*Function{} - for _, f := range p.Functions { - if f.flag { - livefunctions = append(livefunctions, f) - } else { - delete(p.functionMap, f.Function) - } - } - p.Functions = livefunctions - - return nil -}