mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-16 02:33:28 +00:00
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:
+6
-6
@@ -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
@@ -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
@@ -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¶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
|
// 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
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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¶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,
|
// 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user