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