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.
This commit is contained in:
Ayke van Laethem
2020-03-27 23:00:41 +01:00
parent 23e88bfb15
commit b8db79f6a6
11 changed files with 491 additions and 595 deletions
+6 -6
View File
@@ -16,7 +16,7 @@ import (
// slice. This is required by the Go language spec: an index out of bounds must // slice. This is required by the Go language spec: an index out of bounds must
// cause a panic. // cause a panic.
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) { 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 // The //go:nobounds pragma was added to the function to avoid bounds
// checking. // checking.
return 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 // biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases. // logic is the same in both cases.
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { 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 // The //go:nobounds pragma was added to the function to avoid bounds
// checking. // checking.
return 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 // createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake. // 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) { 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 // The //go:nobounds pragma was added to the function to avoid bounds
// checking. // checking.
return 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. // createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
// This function assumes that the shift value is signed. // This function assumes that the shift value is signed.
func (b *builder) createNegativeShiftCheck(shift llvm.Value) { func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
if b.fn.IsNoBounds() { if b.info.nobounds {
// Function disabled bounds checking - skip shift check. // Function disabled bounds checking - skip shift check.
return return
} }
@@ -212,8 +212,8 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
} }
} }
faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw") faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next") nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block. // Now branch to the out-of-bounds or the regular block.
+6 -5
View File
@@ -4,6 +4,7 @@ import (
"go/types" "go/types"
"strconv" "strconv"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -34,14 +35,14 @@ const (
// createCall creates a new call to runtime.<fnName> with the given arguments. // createCall creates a new call to runtime.<fnName> with the given arguments.
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value { func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
fullName := "runtime." + fnName fn := b.ir.Program.ImportedPackage("runtime").Members[fnName].(*ssa.Function)
fn := b.mod.NamedFunction(fullName) llvmFn := b.getFunction(fn)
if fn.IsNil() { if llvmFn.IsNil() {
panic("trying to call non-existent function: " + fullName) panic("trying to call non-existent function: " + fn.RelString(nil))
} }
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle 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 // createCall creates a call to the given function with the arguments possibly
+142 -155
View File
@@ -11,6 +11,7 @@ import (
"go/types" "go/types"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
@@ -60,7 +61,9 @@ type compilerContext struct {
type builder struct { type builder struct {
*compilerContext *compilerContext
llvm.Builder llvm.Builder
fn *ir.Function fn *ssa.Function
llvmFn llvm.Value
info functionInfo
locals map[ssa.Value]llvm.Value // local variables locals map[ssa.Value]llvm.Value // local variables
blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
@@ -71,9 +74,9 @@ type builder struct {
difunc llvm.Metadata difunc llvm.Metadata
dilocals map[*types.Var]llvm.Metadata dilocals map[*types.Var]llvm.Metadata
allDeferFuncs []interface{} allDeferFuncs []interface{}
deferFuncs map[*ir.Function]int deferFuncs map[*ssa.Function]int
deferInvokeFuncs map[string]int deferInvokeFuncs map[string]int
deferClosureFuncs map[*ir.Function]int deferClosureFuncs map[*ssa.Function]int
selectRecvBuf map[*ssa.Select]llvm.Value 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) 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. // Initialize debug information.
if c.Debug() { if c.Debug() {
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{ c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
@@ -269,50 +266,42 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
} }
} }
// Declare all functions. // Predeclare the runtime.alloc function, which is used by the wordpack
for _, f := range c.ir.Functions { // functionality.
c.createFunctionDeclaration(f) 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. // Add definitions to declarations.
var initFuncs []llvm.Value
irbuilder := c.ctx.NewBuilder() irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose() defer irbuilder.Dispose()
for _, f := range c.ir.Functions { for _, pkg := range c.ir.Packages() {
if f.Synthetic == "package initializer" { c.createPackage(pkg, irbuilder)
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()
} }
// After all packages are imported, add a synthetic initializer function // After all packages are imported, add a synthetic initializer function
// that calls the initializer of each package. // that calls the initializer of each package.
initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)) initFn := c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)
initFn.LLVMFn.SetLinkage(llvm.InternalLinkage) llvmInitFn := c.getFunction(initFn)
initFn.LLVMFn.SetUnnamedAddr(true) llvmInitFn.SetLinkage(llvm.InternalLinkage)
llvmInitFn.SetUnnamedAddr(true)
if c.Debug() { if c.Debug() {
difunc := c.attachDebugInfo(initFn) difunc := c.attachDebugInfo(initFn)
pos := c.ir.Program.Fset.Position(initFn.Pos()) pos := c.ir.Program.Fset.Position(initFn.Pos())
irbuilder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) 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) irbuilder.SetInsertPointAtEnd(block)
for _, fn := range initFuncs { for _, fn := range initFuncs {
irbuilder.CreateCall(fn, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType)}, "") 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 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&paramIsDeferenceableOrNull == 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 // attachDebugInfo adds debug info to a function declaration. It returns the
// DISubprogram metadata node. // 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()) 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 // attachDebugInfo adds debug info to a function declaration. It returns the
// DISubprogram metadata node. This method allows some more control over how // DISubprogram metadata node. This method allows some more control over how
// debug info is added to the function. // 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. // Debug info for this function.
diparams := make([]llvm.Metadata, 0, len(f.Params)) diparams := make([]llvm.Metadata, 0, len(f.Params))
for _, param := range 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{ difunc := c.dibuilder.CreateFunction(c.getDIFile(filename), llvm.DIFunction{
Name: f.RelString(nil) + suffix, Name: f.RelString(nil) + suffix,
LinkageName: f.LinkName() + suffix, LinkageName: c.getFunctionInfo(f).linkName + suffix,
File: c.getDIFile(filename), File: c.getDIFile(filename),
Line: line, Line: line,
Type: diFuncType, Type: diFuncType,
@@ -851,37 +762,99 @@ func (c *compilerContext) getDIFile(filename string) llvm.Metadata {
return c.difiles[filename] return c.difiles[filename]
} }
// createFunctionDefinition builds the LLVM IR implementation for this function. func (c *compilerContext) createPackage(pkg *ssa.Package, irbuilder llvm.Builder) {
// The function must be declared but not yet defined, otherwise this function memberNames := make([]string, 0)
// will create a diagnostic. for name := range pkg.Members {
func (b *builder) createFunctionDefinition() { memberNames = append(memberNames, name)
if b.DumpSSA() {
fmt.Printf("\nfunc %s:\n", b.fn.Function)
} }
if !b.fn.LLVMFn.IsDeclaration() { sort.Strings(memberNames)
for _, name := range memberNames {
switch member := pkg.Members[name].(type) {
case *ssa.Function:
llvmFn := c.getFunction(member)
if member.Blocks == nil {
continue // external function
}
c.createFunction(irbuilder, member, llvmFn)
case *ssa.Type:
if types.IsInterface(member.Type()) {
// Interfaces don't have concrete methods.
continue
}
// Named type. We should make sure all methods are created.
// This includes both functions with pointer receivers and those
// without.
methods := getAllMethods(pkg.Prog, member.Type())
methods = append(methods, getAllMethods(pkg.Prog, types.NewPointer(member.Type()))...)
for _, method := range methods {
// Parse this method.
fn := pkg.Prog.MethodValue(method)
if fn.Blocks == nil {
continue // external function
}
if member.Type().String() != member.String() {
// This is a member on a type alias. Do not build such a
// function.
continue
}
c.createFunction(irbuilder, fn, c.getFunction(fn))
}
case *ssa.Global:
// Make sure the global is present and has an initializer.
c.getGlobal(member)
case *ssa.NamedConst:
// TODO: create DWARF entries for these.
default:
panic("unknown member type: " + member.String())
}
}
}
// createFunction builds the LLVM IR implementation for this function. The
// function must not yet be defined, otherwise this function will create a
// diagnostic.
func (c *compilerContext) createFunction(irbuilder llvm.Builder, fn *ssa.Function, llvmFn llvm.Value) {
b := builder{
compilerContext: c,
Builder: irbuilder,
fn: fn,
llvmFn: llvmFn,
info: c.getFunctionInfo(fn),
locals: make(map[ssa.Value]llvm.Value),
dilocals: make(map[*types.Var]llvm.Metadata),
blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock),
blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock),
}
if b.DumpSSA() {
fmt.Printf("\nfunc %s:\n", b.fn)
}
if !b.llvmFn.IsDeclaration() {
errValue := b.fn.Name() + " redeclared in this program" errValue := b.fn.Name() + " redeclared in this program"
fnPos := getPosition(b.fn.LLVMFn) fnPos := getPosition(b.llvmFn)
if fnPos.IsValid() { if fnPos.IsValid() {
errValue += "\n\tprevious declaration at " + fnPos.String() errValue += "\n\tprevious declaration at " + fnPos.String()
} }
b.addError(b.fn.Pos(), errValue) b.addError(b.fn.Pos(), errValue)
return return
} }
if !b.fn.IsExported() { if !b.info.exported {
b.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) b.llvmFn.SetLinkage(llvm.InternalLinkage)
b.fn.LLVMFn.SetUnnamedAddr(true) b.llvmFn.SetUnnamedAddr(true)
} }
// Some functions have a pragma controlling the inlining level. // Some functions have a pragma controlling the inlining level.
switch b.fn.Inline() { switch b.info.inline {
case ir.InlineHint: case inlineHint:
// Add LLVM inline hint to functions with //go:inline pragma. // Add LLVM inline hint to functions with //go:inline pragma.
inline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0) inline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
b.fn.LLVMFn.AddFunctionAttr(inline) b.llvmFn.AddFunctionAttr(inline)
case ir.InlineNone: case inlineNone:
// Add LLVM attribute to always avoid inlining this function. // Add LLVM attribute to always avoid inlining this function.
noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0) noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
b.fn.LLVMFn.AddFunctionAttr(noinline) b.llvmFn.AddFunctionAttr(noinline)
} }
// Add debug info, if needed. // Add debug info, if needed.
@@ -890,7 +863,7 @@ func (b *builder) createFunctionDefinition() {
// Package initializers have no debug info. Create some fake debug // Package initializers have no debug info. Create some fake debug
// info to at least have *something*. // info to at least have *something*.
filename := b.fn.Package().Pkg.Path() + "/<init>" filename := b.fn.Package().Pkg.Path() + "/<init>"
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 { } else if b.fn.Syntax() != nil {
// Create debug info file if needed. // Create debug info file if needed.
b.difunc = b.attachDebugInfo(b.fn) b.difunc = b.attachDebugInfo(b.fn)
@@ -901,7 +874,7 @@ func (b *builder) createFunctionDefinition() {
// Pre-create all basic blocks in the function. // Pre-create all basic blocks in the function.
for _, block := range b.fn.DomPreorder() { 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.blockEntries[block] = llvmBlock
b.blockExits[block] = llvmBlock b.blockExits[block] = llvmBlock
} }
@@ -914,7 +887,7 @@ func (b *builder) createFunctionDefinition() {
llvmType := b.getLLVMType(param.Type()) llvmType := b.getLLVMType(param.Type())
fields := make([]llvm.Value, 0, 1) fields := make([]llvm.Value, 0, 1)
for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) { for _, info := range expandFormalParamType(llvmType, param.Name(), param.Type()) {
param := b.fn.LLVMFn.Param(llvmParamIndex) param := b.llvmFn.Param(llvmParamIndex)
param.SetName(info.name) param.SetName(info.name)
fields = append(fields, param) fields = append(fields, param)
llvmParamIndex++ llvmParamIndex++
@@ -945,8 +918,8 @@ func (b *builder) createFunctionDefinition() {
// Load free variables from the context. This is a closure (or bound // Load free variables from the context. This is a closure (or bound
// method). // method).
var context llvm.Value var context llvm.Value
if !b.fn.IsExported() { if !b.info.exported {
parentHandle := b.fn.LLVMFn.LastParam() parentHandle := b.llvmFn.LastParam()
parentHandle.SetName("parentHandle") parentHandle.SetName("parentHandle")
context = llvm.PrevParam(parentHandle) context = llvm.PrevParam(parentHandle)
context.SetName("context") context.SetName("context")
@@ -1040,6 +1013,11 @@ func (b *builder) createFunctionDefinition() {
b.trackValue(phi.llvm) 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 // 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 { if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new // Static callee is known. This makes it easier to start a new
// goroutine. // goroutine.
calleeFn := b.ir.GetFunction(callee) calleeFn := b.getFunction(callee)
var context llvm.Value var context llvm.Value
switch value := instr.Call.Value.(type) { switch value := instr.Call.Value.(type) {
case *ssa.Function: case *ssa.Function:
@@ -1097,7 +1075,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
panic("StaticCallee returned an unexpected value") panic("StaticCallee returned an unexpected value")
} }
params = append(params, context) // context parameter params = append(params, context) // context parameter
b.createGoInstruction(calleeFn.LLVMFn, params, "", callee.Pos()) b.createGoInstruction(calleeFn, params, "", callee.Pos())
} else if !instr.Call.IsInvoke() { } else if !instr.Call.IsInvoke() {
// This is a function pointer. // This is a function pointer.
// At the moment, two extra params are passed to the newly started // 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])) b.CreateRet(b.getValue(instr.Results[0]))
} else { } else {
// Multiple return values. Put them all in a struct. // 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 { for i, result := range instr.Results {
val := b.getValue(result) val := b.getValue(result)
retVal = b.CreateInsertValue(retVal, val, i, "") retVal = b.CreateInsertValue(retVal, val, i, "")
@@ -1371,7 +1349,15 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
case strings.HasPrefix(name, "device/arm.SVCall"): case strings.HasPrefix(name, "device/arm.SVCall"):
return b.emitSVCall(instr.Args) return b.emitSVCall(instr.Args)
case strings.HasPrefix(name, "(device/riscv.CSR)."): 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"): case strings.HasPrefix(name, "syscall.Syscall"):
return b.createSyscall(instr) return b.createSyscall(instr)
case strings.HasPrefix(name, "runtime/volatile.Load"): case strings.HasPrefix(name, "runtime/volatile.Load"):
@@ -1382,9 +1368,10 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.createInterruptGlobal(instr) return b.createInterruptGlobal(instr)
} }
targetFunc := b.ir.GetFunction(fn) callee = b.getFunction(fn)
if targetFunc.LLVMFn.IsNil() { info := b.getFunctionInfo(fn)
return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName()) if callee.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "undefined function: "+info.linkName)
} }
switch value := instr.Value.(type) { switch value := instr.Value.(type) {
case *ssa.Function: case *ssa.Function:
@@ -1398,8 +1385,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
default: default:
panic("StaticCallee returned an unexpected value") panic("StaticCallee returned an unexpected value")
} }
callee = targetFunc.LLVMFn exported = info.exported
exported = targetFunc.IsExported()
} else if call, ok := instr.Value.(*ssa.Builtin); ok { } else if call, ok := instr.Value.(*ssa.Builtin); ok {
// Builtin function (append, close, delete, etc.).) // Builtin function (append, close, delete, etc.).)
return b.createBuiltin(instr.Args, call.Name(), instr.Pos()) return b.createBuiltin(instr.Args, call.Name(), instr.Pos())
@@ -1434,14 +1420,15 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
func (b *builder) getValue(expr ssa.Value) llvm.Value { func (b *builder) getValue(expr ssa.Value) llvm.Value {
switch expr := expr.(type) { switch expr := expr.(type) {
case *ssa.Const: case *ssa.Const:
return b.createConst(b.fn.LinkName(), expr) return b.createConst(b.info.linkName, expr)
case *ssa.Function: case *ssa.Function:
fn := b.ir.GetFunction(expr) info := b.getFunctionInfo(expr)
if fn.IsExported() { if info.exported {
b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String()) b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String())
return llvm.Undef(b.getLLVMType(expr.Type())) 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: case *ssa.Global:
value := b.getGlobal(expr) value := b.getGlobal(expr)
if value.IsNil() { if value.IsNil() {
+17 -19
View File
@@ -15,7 +15,6 @@ package compiler
import ( import (
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -25,9 +24,9 @@ import (
// calls. // calls.
func (b *builder) deferInitFunc() { func (b *builder) deferInitFunc() {
// Some setup. // Some setup.
b.deferFuncs = make(map[*ir.Function]int) b.deferFuncs = make(map[*ssa.Function]int)
b.deferInvokeFuncs = make(map[string]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. // Create defer list pointer.
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0) 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 { } else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call. // Regular function call.
fn := b.ir.GetFunction(callee)
if _, ok := b.deferFuncs[fn]; !ok { if _, ok := b.deferFuncs[callee]; !ok {
b.deferFuncs[fn] = len(b.allDeferFuncs) b.deferFuncs[callee] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, fn) 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 // Collect all values to be put in the struct (starting with
// runtime._defer fields). // runtime._defer fields).
@@ -132,7 +130,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
context := b.CreateExtractValue(closure, 0, "") context := b.CreateExtractValue(closure, 0, "")
// Get the callback number. // Get the callback number.
fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function)) fn := makeClosure.Fn.(*ssa.Function)
if _, ok := b.deferClosureFuncs[fn]; !ok { if _, ok := b.deferClosureFuncs[fn]; !ok {
b.deferClosureFuncs[fn] = len(b.allDeferFuncs) b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure) b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
@@ -203,10 +201,10 @@ func (b *builder) createRunDefers() {
// } // }
// Create loop. // Create loop.
loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead") loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop") loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default") unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end") end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
b.CreateBr(loophead) b.CreateBr(loophead)
// Create loop head: // Create loop head:
@@ -238,7 +236,7 @@ func (b *builder) createRunDefers() {
// Create switch case, for example: // Create switch case, for example:
// case 0: // case 0:
// // run first deferred call // // 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) sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block) b.SetInsertPointAtEnd(block)
switch callback := callback.(type) { switch callback := callback.(type) {
@@ -279,7 +277,7 @@ func (b *builder) createRunDefers() {
fnPtr := b.getInvokePtr(callback, typecode) fnPtr := b.getInvokePtr(callback, typecode)
b.createCall(fnPtr, forwardParams, "") b.createCall(fnPtr, forwardParams, "")
case *ir.Function: case *ssa.Function:
// Direct call. // Direct call.
// Get the real defer struct type and cast to it. // 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. // 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. // 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 // Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway. // function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
@@ -311,11 +309,11 @@ func (b *builder) createRunDefers() {
} }
// Call real function. // Call real function.
b.createCall(callback.LLVMFn, forwardParams, "") b.createCall(b.getFunction(callback), forwardParams, "")
case *ssa.MakeClosure: case *ssa.MakeClosure:
// Get the real defer struct type and cast to it. // 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)} valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params() params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
@@ -338,7 +336,7 @@ func (b *builder) createRunDefers() {
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Call deferred function. // Call deferred function.
b.createCall(fn.LLVMFn, forwardParams, "") b.createCall(b.getFunction(fn), forwardParams, "")
default: default:
panic("unknown deferred function type") panic("unknown deferred function type")
+12 -2
View File
@@ -5,6 +5,7 @@ package compiler
import ( import (
"go/types" "go/types"
"strings"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa" "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 { if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables") 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. // Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings)) 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) context := b.emitPointerPack(boundVars)
// Create the closure. // Create the closure.
return b.createFuncValue(f.LLVMFn, context, f.Signature), nil return b.createFuncValue(llvmFn, context, f.Signature), nil
} }
+3 -1
View File
@@ -7,6 +7,7 @@ import (
"go/token" "go/token"
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -28,7 +29,8 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
default: default:
panic("unreachable") 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()) return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
} }
+38 -18
View File
@@ -247,15 +247,23 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
for i := 0; i < ms.Len(); i++ { for i := 0; i < ms.Len(); i++ {
method := ms.At(i) method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
f := c.ir.GetFunction(c.ir.Program.MethodValue(method)) fn := c.ir.Program.MethodValue(method)
if f.LLVMFn.IsNil() { llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
// compiler error, so panic // 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{ methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal, signatureGlobal,
llvm.ConstPtrToInt(fn, c.uintptrType), llvm.ConstPtrToInt(wrapper, c.uintptrType),
}) })
methods[i] = methodInfo methods[i] = methodInfo
} }
@@ -357,8 +365,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// value. // value.
prevBlock := b.GetInsertBlock() prevBlock := b.GetInsertBlock()
okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok") okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next") nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock) 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. // 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 // If the method to wrap has a pointer receiver, no wrapping is necessary and
// the function is returned directly. // the function is returned directly.
func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value { func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
wrapperName := f.LinkName() + "$invoke" wrapperName := llvmFn.Name() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName) wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() { if !wrapper.IsNil() {
// Wrapper already created. Return it directly. // Wrapper already created. Return it directly.
@@ -445,7 +453,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
} }
// Get the expanded receiver type. // Get the expanded receiver type.
receiverType := c.getLLVMType(f.Params[0].Type()) receiverType := c.getLLVMType(fn.Params[0].Type())
var expandedReceiverType []llvm.Type var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) { for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType) 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 // Casting a function signature to a different signature and calling it
// with a receiver pointer bitcasted to *i8 (as done in calls on an // with a receiver pointer bitcasted to *i8 (as done in calls on an
// interface) is hopefully a safe (defined) operation. // interface) is hopefully a safe (defined) operation.
return f.LLVMFn return llvmFn
} }
// create wrapper function // create wrapper function
fnType := f.LLVMFn.Type().ElementType() fnType := llvmFn.Type().ElementType()
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...) paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false) wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
if f.LLVMFn.LastParam().Name() == "parentHandle" { if llvmFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle") wrapper.LastParam().SetName("parentHandle")
} }
@@ -481,8 +489,8 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// add debug info if needed // add debug info if needed
if c.Debug() { if c.Debug() {
pos := c.ir.Program.Fset.Position(f.Pos()) pos := c.ir.Program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line) difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) 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] receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...) params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind { if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(f.LLVMFn, params, "") b.CreateCall(llvmFn, params, "")
b.CreateRetVoid() b.CreateRetVoid()
} else { } else {
ret := b.CreateCall(f.LLVMFn, params, "ret") ret := b.CreateCall(llvmFn, params, "ret")
b.CreateRet(ret) b.CreateRet(ret)
} }
return wrapper 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
}
+211
View File
@@ -15,6 +15,197 @@ import (
"tinygo.org/x/go-llvm" "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&paramIsDeferenceableOrNull == 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, // 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 // 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). // 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
}
+56 -235
View File
@@ -1,14 +1,10 @@
package ir package ir
import ( import (
"go/ast"
"go/types" "go/types"
"sort"
"strings"
"github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
) )
// This file provides a wrapper around go/ssa values and adds extra // This file provides a wrapper around go/ssa values and adds extra
@@ -20,48 +16,9 @@ type Program struct {
Program *ssa.Program Program *ssa.Program
LoaderProgram *loader.Program LoaderProgram *loader.Program
mainPkg *ssa.Package mainPkg *ssa.Package
Functions []*Function mainPath string
functionMap map[*ssa.Function]*Function
} }
// Function or method.
type Function struct {
*ssa.Function
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
inline InlineType // go:inline
}
// Interface type that is at some point used in a type assert (to check whether
// it implements another interface).
type Interface struct {
Num int
Type *types.Interface
}
type InlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
InlineDefault InlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
InlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
InlineNone
)
// Create and initialize a new *Program from a *ssa.Program. // Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program, mainPath string) *Program { func NewProgram(lprogram *loader.Program, mainPath string) *Program {
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
@@ -84,17 +41,26 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
panic("could not find main package") 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{} packageList := []*ssa.Package{}
packageSet := map[string]struct{}{} packageSet := map[string]struct{}{}
worklist := []string{"runtime", mainPath} worklist := []string{"runtime", p.mainPath}
for len(worklist) != 0 { for len(worklist) != 0 {
pkgPath := worklist[0] pkgPath := worklist[0]
var pkg *ssa.Package var pkg *ssa.Package
if pkgPath == mainPath { if pkgPath == p.mainPath {
pkg = mainPkg // necessary for compiling individual .go files pkg = p.mainPkg // necessary for compiling individual .go files
} else { } else {
pkg = program.ImportedPackage(pkgPath) pkg = p.Program.ImportedPackage(pkgPath)
} }
if pkg == nil { if pkg == nil {
// Non-SSA package (e.g. cgo). // Non-SSA package (e.g. cgo).
@@ -130,201 +96,56 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
} }
} }
p := &Program{ return packageList
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]
} }
func (p *Program) MainPkg() *ssa.Package { func (p *Program) MainPkg() *ssa.Package {
return p.mainPkg return p.mainPkg
} }
// Parse compiler directives in the preceding comments. // MethodSignature creates a readable version of a method signature (including
func (f *Function) parsePragmas() { // the function name, excluding the receiver name). This string is used
if f.Syntax() == nil { // internally to match interfaces and to call the correct method on an
return // interface. Examples:
} //
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { // String() string
for _, comment := range decl.Doc.List { // Read([]byte) (int, error)
text := comment.Text func MethodSignature(method *types.Func) string {
if strings.HasPrefix(text, "//export ") { return method.Name() + signature(method.Type().(*types.Signature))
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
continue
}
f.linkName = parts[1]
f.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
f.module = parts[1]
case "//go:inline":
f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
f.linkName = parts[2]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
f.nobounds = true
}
}
}
}
} }
func (f *Function) IsNoBounds() bool { // Make a readable version of a function (pointer) signature.
return f.nobounds // Examples:
} //
// () string
// Return true iff this function is externally visible. // (string, int) (int, error)
func (f *Function) IsExported() bool { func signature(sig *types.Signature) string {
return f.exported || f.CName() != "" s := ""
} if sig.Params().Len() == 0 {
s += "()"
// Return the inline directive of this function.
func (f *Function) Inline() InlineType {
return f.inline
}
// Return the module name if not the default.
func (f *Function) Module() string {
return f.module
}
// Return the link name for this function.
func (f *Function) LinkName() string {
if f.linkName != "" {
return f.linkName
}
if f.Signature.Recv() != nil {
// Method on a defined type (which may be a pointer).
return f.RelString(nil)
} else { } else {
// Bare function. s += "("
if name := f.CName(); name != "" { for i := 0; i < sig.Params().Len(); i++ {
// Name CGo functions directly. if i > 0 {
return name s += ", "
} else { }
return f.RelString(nil) s += sig.Params().At(i).Type().String()
} }
s += ")"
} }
} if sig.Results().Len() == 0 {
// keep as-is
// Return the name of the C function if this is a CGo wrapper. Otherwise, return } else if sig.Results().Len() == 1 {
// a zero-length string. s += " " + sig.Results().At(0).Type().String()
func (f *Function) CName() string { } else {
name := f.Name() s += " ("
if strings.HasPrefix(name, "_Cfunc_") { for i := 0; i < sig.Results().Len(); i++ {
// emitted by `go tool cgo` if i > 0 {
return name[len("_Cfunc_"):] s += ", "
} }
if strings.HasPrefix(name, "C.") { s += sig.Results().At(i).Type().String()
// created by ../loader/cgo.go }
return name[2:] s += ")"
} }
return "" return s
}
// 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
} }
-149
View File
@@ -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
}
-5
View File
@@ -85,14 +85,9 @@ type taskHolder interface {
getReturnPtr() unsafe.Pointer 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() { func fake() {
// Hack to ensure intrinsics are discovered. // Hack to ensure intrinsics are discovered.
Current() Current()
go func() {}()
Pause() Pause()
} }