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