transform: remove switched func lowering
The switched func lowering was mainly necessary for coroutines. With coroutines removed, this is no longer necessary.
Этот коммит содержится в:
родитель
ea2a6b70b2
коммит
0c2fefa09b
14 изменённых файлов: 57 добавлений и 617 удалений
|
@ -153,7 +153,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
|
|||
SizeLevel: sizeLevel,
|
||||
|
||||
Scheduler: config.Scheduler(),
|
||||
FuncImplementation: config.FuncImplementation(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
DefaultStackSize: config.Target.DefaultStackSize,
|
||||
NeedsStackObjects: config.NeedsStackObjects(),
|
||||
|
|
|
@ -159,28 +159,6 @@ func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
|
|||
}
|
||||
}
|
||||
|
||||
// FuncImplementation picks an appropriate func value implementation for the
|
||||
// target.
|
||||
func (c *Config) FuncImplementation() string {
|
||||
switch c.Scheduler() {
|
||||
case "tasks", "asyncify":
|
||||
// A func value is implemented as a pair of pointers:
|
||||
// {context, function pointer}
|
||||
// where the context may be a pointer to a heap-allocated struct
|
||||
// containing the free variables, or it may be undef if the function
|
||||
// being pointed to doesn't need a context. The function pointer is a
|
||||
// regular function pointer.
|
||||
return "doubleword"
|
||||
case "none":
|
||||
// As "doubleword", but with the function pointer replaced by a unique
|
||||
// ID per function signature. Function values are called by using a
|
||||
// switch statement and choosing which function to call.
|
||||
return "switch"
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
}
|
||||
|
||||
// PanicStrategy returns the panic strategy selected for this target. Valid
|
||||
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
||||
// instruction).
|
||||
|
|
|
@ -47,7 +47,6 @@ type Config struct {
|
|||
|
||||
// Various compiler options that determine how code is generated.
|
||||
Scheduler string
|
||||
FuncImplementation string
|
||||
AutomaticStackSize bool
|
||||
DefaultStackSize uint64
|
||||
NeedsStackObjects bool
|
||||
|
|
|
@ -55,7 +55,7 @@ func TestCompiler(t *testing.T) {
|
|||
{"string.go", "", ""},
|
||||
{"float.go", "", ""},
|
||||
{"interface.go", "", ""},
|
||||
{"func.go", "", "none"},
|
||||
{"func.go", "", ""},
|
||||
{"pragma.go", "", ""},
|
||||
{"goroutine.go", "wasm", "asyncify"},
|
||||
{"goroutine.go", "cortex-m-qemu", "tasks"},
|
||||
|
@ -105,7 +105,6 @@ func TestCompiler(t *testing.T) {
|
|||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
FuncImplementation: config.FuncImplementation(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
DefaultStackSize: config.Target.DefaultStackSize,
|
||||
NeedsStackObjects: config.NeedsStackObjects(),
|
||||
|
|
|
@ -19,29 +19,8 @@ func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signat
|
|||
// createFuncValue creates a function value from a raw function pointer with no
|
||||
// context.
|
||||
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
||||
var funcValueScalar llvm.Value
|
||||
switch c.FuncImplementation {
|
||||
case "doubleword":
|
||||
// Closure is: {context, function pointer}
|
||||
funcValueScalar = llvm.ConstBitCast(funcPtr, c.rawVoidFuncType)
|
||||
case "switch":
|
||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||
if funcValueWithSignatureGlobal.IsNil() {
|
||||
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
|
||||
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
||||
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
||||
c.getFuncSignatureID(sig),
|
||||
})
|
||||
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
|
||||
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
|
||||
funcValueWithSignatureGlobal.SetGlobalConstant(true)
|
||||
funcValueWithSignatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
}
|
||||
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
funcValueScalar := llvm.ConstBitCast(funcPtr, c.rawVoidFuncType)
|
||||
funcValueType := c.getFuncType(sig)
|
||||
funcValue := llvm.Undef(funcValueType)
|
||||
funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
|
||||
|
@ -78,8 +57,6 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
|||
// value. This may be an expensive operation.
|
||||
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
|
||||
context = b.CreateExtractValue(funcValue, 0, "")
|
||||
switch b.FuncImplementation {
|
||||
case "doubleword":
|
||||
bitcast := b.CreateExtractValue(funcValue, 1, "")
|
||||
if !bitcast.IsAConstantExpr().IsNil() && bitcast.Opcode() == llvm.BitCast {
|
||||
funcPtr = bitcast.Operand(0)
|
||||
|
@ -87,34 +64,12 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
|
|||
}
|
||||
llvmSig := b.getRawFuncType(sig)
|
||||
funcPtr = b.CreateBitCast(bitcast, llvmSig, "")
|
||||
case "switch":
|
||||
if !funcValue.IsAConstant().IsNil() {
|
||||
// If this is a constant func value, the underlying function is
|
||||
// known and can be returned directly.
|
||||
funcValueWithSignatureGlobal := llvm.ConstExtractValue(funcValue, []uint32{1}).Operand(0)
|
||||
funcPtr = llvm.ConstExtractValue(funcValueWithSignatureGlobal.Initializer(), []uint32{0}).Operand(0)
|
||||
return
|
||||
}
|
||||
llvmSig := b.getRawFuncType(sig)
|
||||
sigGlobal := b.getFuncSignatureID(sig)
|
||||
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getFuncType returns the type of a func value given a signature.
|
||||
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
|
||||
switch c.FuncImplementation {
|
||||
case "doubleword":
|
||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, c.rawVoidFuncType}, false)
|
||||
case "switch":
|
||||
return c.getLLVMRuntimeType("funcValue")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
}
|
||||
|
||||
// getRawFuncType returns a LLVM function pointer type for a given signature.
|
||||
|
|
55
compiler/testdata/func-none.ll
предоставленный
55
compiler/testdata/func-none.ll
предоставленный
|
@ -1,55 +0,0 @@
|
|||
; ModuleID = 'func.go'
|
||||
source_filename = "func.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
%runtime.funcValueWithSignature = type { i32, i8* }
|
||||
|
||||
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
|
||||
@"main.someFunc$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @main.someFunc to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
|
||||
|
||||
declare void @runtime.trackPointer(i8* nocapture readonly, i8*, i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.foo(i8* %callback.context, i32 %callback.funcptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = call i32 @runtime.getFuncPtr(i8* %callback.context, i32 %callback.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null) #0
|
||||
%1 = icmp eq i32 %0, 0
|
||||
br i1 %1, label %fpcall.throw, label %fpcall.next
|
||||
|
||||
fpcall.throw: ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef, i8* null) #0
|
||||
unreachable
|
||||
|
||||
fpcall.next: ; preds = %entry
|
||||
%2 = inttoptr i32 %0 to void (i32, i8*, i8*)*
|
||||
call void %2(i32 3, i8* %callback.context, i8* undef) #0
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
|
||||
|
||||
declare void @runtime.nilPanic(i8*, i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.bar(i8* %context, i8* %parentHandle) unnamed_addr #0 {
|
||||
entry:
|
||||
call void @main.foo(i8* undef, i32 ptrtoint (%runtime.funcValueWithSignature* @"main.someFunc$withSignature" to i32), i8* undef, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.someFunc(i32 %arg0, i8* %context, i8* %parentHandle) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
attributes #0 = { nounwind }
|
47
compiler/testdata/func.ll
предоставленный
Обычный файл
47
compiler/testdata/func.ll
предоставленный
Обычный файл
|
@ -0,0 +1,47 @@
|
|||
; ModuleID = 'func.go'
|
||||
source_filename = "func.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
|
||||
|
||||
declare void @runtime.trackPointer(i8* nocapture readonly, i8*, i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.foo(i8* %callback.context, void ()* %callback.funcptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp eq void ()* %callback.funcptr, null
|
||||
br i1 %0, label %fpcall.throw, label %fpcall.next
|
||||
|
||||
fpcall.throw: ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef, i8* null) #0
|
||||
unreachable
|
||||
|
||||
fpcall.next: ; preds = %entry
|
||||
%1 = bitcast void ()* %callback.funcptr to void (i32, i8*, i8*)*
|
||||
call void %1(i32 3, i8* %callback.context, i8* undef) #0
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.nilPanic(i8*, i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.bar(i8* %context, i8* %parentHandle) unnamed_addr #0 {
|
||||
entry:
|
||||
call void @main.foo(i8* undef, void ()* bitcast (void (i32, i8*, i8*)* @main.someFunc to void ()*), i8* undef, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.someFunc(i32 %arg0, i8* %context, i8* %parentHandle) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
attributes #0 = { nounwind }
|
5
compiler/testdata/gc.go
предоставленный
5
compiler/testdata/gc.go
предоставленный
|
@ -61,11 +61,6 @@ func newStruct() {
|
|||
}
|
||||
|
||||
func newFuncValue() *func() {
|
||||
// On some platforms that use runtime.funcValue ("switch" style) function
|
||||
// values, a func value is allocated as having two pointer words while the
|
||||
// struct looks like {unsafe.Pointer; uintptr}. This is so that the interp
|
||||
// package won't get confused, see getPointerBitmap in compiler/llvm.go for
|
||||
// details.
|
||||
return new(func())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,285 +0,0 @@
|
|||
package transform
|
||||
|
||||
// This file lowers func values into their final form. This is necessary for
|
||||
// funcValueSwitch, which needs full program analysis.
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// funcSignatureInfo keeps information about a single signature and its uses.
|
||||
type funcSignatureInfo struct {
|
||||
sig llvm.Value // *uint8 to identify the signature
|
||||
funcValueWithSignatures []llvm.Value // slice of runtime.funcValueWithSignature
|
||||
}
|
||||
|
||||
// funcWithUses keeps information about a single function used as func value and
|
||||
// the assigned function ID. More commonly used functions are assigned a lower
|
||||
// ID.
|
||||
type funcWithUses struct {
|
||||
funcPtr llvm.Value
|
||||
useCount int // how often this function is used in a func value
|
||||
id int // assigned ID
|
||||
}
|
||||
|
||||
// Slice to sort functions by their use counts, or else their name if they're
|
||||
// used equally often.
|
||||
type funcWithUsesList []*funcWithUses
|
||||
|
||||
func (l funcWithUsesList) Len() int { return len(l) }
|
||||
func (l funcWithUsesList) Less(i, j int) bool {
|
||||
if l[i].useCount != l[j].useCount {
|
||||
// return the reverse: we want the highest use counts sorted first
|
||||
return l[i].useCount > l[j].useCount
|
||||
}
|
||||
iName := l[i].funcPtr.Name()
|
||||
jName := l[j].funcPtr.Name()
|
||||
return iName < jName
|
||||
}
|
||||
func (l funcWithUsesList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
// LowerFuncValues lowers the runtime.funcValueWithSignature type and
|
||||
// runtime.getFuncPtr function to their final form.
|
||||
func LowerFuncValues(mod llvm.Module) {
|
||||
ctx := mod.Context()
|
||||
builder := ctx.NewBuilder()
|
||||
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||
|
||||
// Find all func values used in the program with their signatures.
|
||||
signatures := map[string]*funcSignatureInfo{}
|
||||
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
var sig, funcVal llvm.Value
|
||||
switch {
|
||||
case strings.HasSuffix(global.Name(), "$withSignature"):
|
||||
sig = llvm.ConstExtractValue(global.Initializer(), []uint32{1})
|
||||
funcVal = global
|
||||
case strings.HasPrefix(global.Name(), "reflect/types.funcid:func:{"):
|
||||
sig = global
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
name := sig.Name()
|
||||
var funcValueWithSignatures []llvm.Value
|
||||
if funcVal.IsNil() {
|
||||
funcValueWithSignatures = []llvm.Value{}
|
||||
} else {
|
||||
funcValueWithSignatures = []llvm.Value{funcVal}
|
||||
}
|
||||
if info, ok := signatures[name]; ok {
|
||||
info.funcValueWithSignatures = append(info.funcValueWithSignatures, funcValueWithSignatures...)
|
||||
} else {
|
||||
signatures[name] = &funcSignatureInfo{
|
||||
sig: sig,
|
||||
funcValueWithSignatures: funcValueWithSignatures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the signatures, for deterministic execution.
|
||||
names := make([]string, 0, len(signatures))
|
||||
for name := range signatures {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
for _, name := range names {
|
||||
info := signatures[name]
|
||||
functions := make(funcWithUsesList, len(info.funcValueWithSignatures))
|
||||
for i, use := range info.funcValueWithSignatures {
|
||||
var useCount int
|
||||
for _, use2 := range getUses(use) {
|
||||
useCount += len(getUses(use2))
|
||||
}
|
||||
functions[i] = &funcWithUses{
|
||||
funcPtr: llvm.ConstExtractValue(use.Initializer(), []uint32{0}).Operand(0),
|
||||
useCount: useCount,
|
||||
}
|
||||
}
|
||||
sort.Sort(functions)
|
||||
|
||||
for i, fn := range functions {
|
||||
fn.id = i + 1
|
||||
for _, ptrtoint := range getUses(fn.funcPtr) {
|
||||
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||
continue
|
||||
}
|
||||
for _, funcValueWithSignatureConstant := range getUses(ptrtoint) {
|
||||
if !funcValueWithSignatureConstant.IsACallInst().IsNil() && funcValueWithSignatureConstant.CalledValue().Name() == "runtime.makeGoroutine" {
|
||||
// makeGoroutine calls are handled seperately
|
||||
continue
|
||||
}
|
||||
for _, funcValueWithSignatureGlobal := range getUses(funcValueWithSignatureConstant) {
|
||||
id := llvm.ConstInt(uintptrType, uint64(fn.id), false)
|
||||
for _, use := range getUses(funcValueWithSignatureGlobal) {
|
||||
// Try to replace uses directly: most will be
|
||||
// ptrtoint instructions.
|
||||
if !use.IsAConstantExpr().IsNil() && use.Opcode() == llvm.PtrToInt {
|
||||
use.ReplaceAllUsesWith(id)
|
||||
}
|
||||
}
|
||||
// Remaining uses can be replaced using a ptrtoint.
|
||||
// In my quick testing, this doesn't really happen in
|
||||
// practice.
|
||||
funcValueWithSignatureGlobal.ReplaceAllUsesWith(llvm.ConstIntToPtr(id, funcValueWithSignatureGlobal.Type()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, getFuncPtrCall := range getUses(info.sig) {
|
||||
if getFuncPtrCall.IsACallInst().IsNil() {
|
||||
continue
|
||||
}
|
||||
if getFuncPtrCall.CalledValue().Name() != "runtime.getFuncPtr" {
|
||||
panic("expected all call uses to be runtime.getFuncPtr")
|
||||
}
|
||||
funcID := getFuncPtrCall.Operand(1)
|
||||
|
||||
// There are functions used in a func value that
|
||||
// implement this signature.
|
||||
// What we'll do is transform the following:
|
||||
// rawPtr := runtime.getFuncPtr(func.ptr)
|
||||
// if rawPtr == nil {
|
||||
// runtime.nilPanic()
|
||||
// }
|
||||
// result := rawPtr(...args, func.context)
|
||||
// into this:
|
||||
// if false {
|
||||
// runtime.nilPanic()
|
||||
// }
|
||||
// var result // Phi
|
||||
// switch fn.id {
|
||||
// case 0:
|
||||
// runtime.nilPanic()
|
||||
// case 1:
|
||||
// result = call first implementation...
|
||||
// case 2:
|
||||
// result = call second implementation...
|
||||
// default:
|
||||
// unreachable
|
||||
// }
|
||||
|
||||
// Remove some casts, checks, and the old call which we're going
|
||||
// to replace.
|
||||
for _, callIntPtr := range getUses(getFuncPtrCall) {
|
||||
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "internal/task.start" {
|
||||
// Special case for goroutine starts.
|
||||
addFuncLoweringSwitch(mod, builder, funcID, callIntPtr, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
calleeValue := builder.CreatePtrToInt(funcPtr, uintptrType, "")
|
||||
start := mod.NamedFunction("internal/task.start")
|
||||
builder.CreateCall(start, []llvm.Value{calleeValue, callIntPtr.Operand(1), llvm.Undef(uintptrType), llvm.Undef(i8ptrType), llvm.ConstNull(i8ptrType)}, "")
|
||||
return llvm.Value{} // void so no return value
|
||||
}, functions)
|
||||
callIntPtr.EraseFromParentAsInstruction()
|
||||
continue
|
||||
}
|
||||
if callIntPtr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
}
|
||||
for _, ptrUse := range getUses(callIntPtr) {
|
||||
if !ptrUse.IsAICmpInst().IsNil() {
|
||||
ptrUse.ReplaceAllUsesWith(llvm.ConstInt(ctx.Int1Type(), 0, false))
|
||||
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == callIntPtr {
|
||||
addFuncLoweringSwitch(mod, builder, funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
return builder.CreateCall(funcPtr, params, "")
|
||||
}, functions)
|
||||
} else {
|
||||
panic("unexpected getFuncPtrCall")
|
||||
}
|
||||
ptrUse.EraseFromParentAsInstruction()
|
||||
}
|
||||
callIntPtr.EraseFromParentAsInstruction()
|
||||
}
|
||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Clean up all globals used before func lowering.
|
||||
for _, obj := range info.funcValueWithSignatures {
|
||||
obj.EraseFromParentAsGlobal()
|
||||
}
|
||||
info.sig.EraseFromParentAsGlobal()
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncLoweringSwitch creates a new switch on a function ID and inserts calls
|
||||
// to the newly created direct calls. The funcID is the number to switch on,
|
||||
// call is the call instruction to replace, and createCall is the callback that
|
||||
// actually creates the new call. By changing createCall to something other than
|
||||
// builder.CreateCall, instead of calling a function it can start a new
|
||||
// goroutine for example.
|
||||
func addFuncLoweringSwitch(mod llvm.Module, builder llvm.Builder, funcID, call llvm.Value, createCall func(funcPtr llvm.Value, params []llvm.Value) llvm.Value, functions funcWithUsesList) {
|
||||
ctx := mod.Context()
|
||||
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
|
||||
// The block that cannot be reached with correct funcValues (to help the
|
||||
// optimizer).
|
||||
builder.SetInsertPointBefore(call)
|
||||
defaultBlock := ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
|
||||
builder.SetInsertPointAtEnd(defaultBlock)
|
||||
builder.CreateUnreachable()
|
||||
|
||||
// Create the switch.
|
||||
builder.SetInsertPointBefore(call)
|
||||
sw := builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
||||
|
||||
// Split right after the switch. We will need to insert a few basic blocks
|
||||
// in this gap.
|
||||
nextBlock := llvmutil.SplitBasicBlock(builder, sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
|
||||
|
||||
// Temporarily set the insert point to set the correct debug insert location
|
||||
// for the builder. It got destroyed by the SplitBasicBlock call.
|
||||
builder.SetInsertPointBefore(call)
|
||||
|
||||
// The 0 case, which is actually a nil check.
|
||||
nilBlock := ctx.InsertBasicBlock(nextBlock, "func.nil")
|
||||
builder.SetInsertPointAtEnd(nilBlock)
|
||||
nilPanic := mod.NamedFunction("runtime.nilPanic")
|
||||
builder.CreateCall(nilPanic, []llvm.Value{llvm.Undef(i8ptrType), llvm.ConstNull(i8ptrType)}, "")
|
||||
builder.CreateUnreachable()
|
||||
sw.AddCase(llvm.ConstInt(uintptrType, 0, false), nilBlock)
|
||||
|
||||
// Gather the list of parameters for every call we're going to make.
|
||||
callParams := make([]llvm.Value, call.OperandsCount()-1)
|
||||
for i := range callParams {
|
||||
callParams[i] = call.Operand(i)
|
||||
}
|
||||
|
||||
// If the call produces a value, we need to get it using a PHI
|
||||
// node.
|
||||
phiBlocks := make([]llvm.BasicBlock, len(functions))
|
||||
phiValues := make([]llvm.Value, len(functions))
|
||||
for i, fn := range functions {
|
||||
// Insert a switch case.
|
||||
bb := ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
||||
builder.SetInsertPointAtEnd(bb)
|
||||
result := createCall(fn.funcPtr, callParams)
|
||||
builder.CreateBr(nextBlock)
|
||||
sw.AddCase(llvm.ConstInt(uintptrType, uint64(fn.id), false), bb)
|
||||
phiBlocks[i] = bb
|
||||
phiValues[i] = result
|
||||
}
|
||||
if call.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
if len(functions) > 0 {
|
||||
// Create the PHI node so that the call result flows into the
|
||||
// next block (after the split). This is only necessary when the
|
||||
// call produced a value.
|
||||
builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||
phi := builder.CreatePHI(call.Type(), "")
|
||||
phi.AddIncoming(phiValues, phiBlocks)
|
||||
call.ReplaceAllUsesWith(phi)
|
||||
} else {
|
||||
// This is always a nil panic, so replace the call result with undef.
|
||||
call.ReplaceAllUsesWith(llvm.Undef(call.Type()))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package transform_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/transform"
|
||||
)
|
||||
|
||||
func TestFuncLowering(t *testing.T) {
|
||||
t.Parallel()
|
||||
testTransform(t, "testdata/func-lowering", transform.LowerFuncValues)
|
||||
}
|
|
@ -78,10 +78,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
|||
return errs
|
||||
}
|
||||
|
||||
if config.FuncImplementation() == "switch" {
|
||||
LowerFuncValues(mod)
|
||||
}
|
||||
|
||||
// After interfaces are lowered, there are many more opportunities for
|
||||
// interprocedural optimizations. To get them to work, function
|
||||
// attributes have to be updated first.
|
||||
|
@ -102,9 +98,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
|||
return []error{err}
|
||||
}
|
||||
LowerReflect(mod)
|
||||
if config.FuncImplementation() == "switch" {
|
||||
LowerFuncValues(mod)
|
||||
}
|
||||
errs := LowerInterrupts(mod)
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
|
|
71
transform/testdata/func-lowering.ll
предоставленный
71
transform/testdata/func-lowering.ll
предоставленный
|
@ -1,71 +0,0 @@
|
|||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32-unknown-unknown-wasm"
|
||||
|
||||
%runtime.funcValueWithSignature = type { i32, i8* }
|
||||
|
||||
@"reflect/types.funcid:func:{basic:uint8}{}" = external constant i8
|
||||
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
|
||||
@"reflect/types.funcid:func:{}{basic:uint32}" = external constant i8
|
||||
@"func1Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func1Uint8 to i32), i8* @"reflect/types.funcid:func:{basic:uint8}{}" }
|
||||
@"func2Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func2Uint8 to i32), i8* @"reflect/types.funcid:func:{basic:uint8}{}" }
|
||||
@"main$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
|
||||
@"main$2$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$2" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
|
||||
|
||||
declare i32 @runtime.getFuncPtr(i8*, i32, i8*, i8*, i8*)
|
||||
|
||||
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.nilPanic(i8*, i8*)
|
||||
|
||||
declare void @"main$1"(i32, i8*, i8*)
|
||||
|
||||
declare void @"main$2"(i32, i8*, i8*)
|
||||
|
||||
declare void @func1Uint8(i8, i8*, i8*)
|
||||
|
||||
declare void @func2Uint8(i8, i8*, i8*)
|
||||
|
||||
; There are no functions with this signature used in a func value.
|
||||
; This means that this should unconditionally nil panic.
|
||||
define i32 @runFuncNone(i8*, i32, i8* %context, i8* %parentHandle) {
|
||||
entry:
|
||||
%2 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, i8* @"reflect/types.funcid:func:{}{basic:uint32}", i8* undef, i8* null)
|
||||
%3 = inttoptr i32 %2 to i32 (i8*, i8*)*
|
||||
%4 = icmp eq i32 (i8*, i8*)* %3, null
|
||||
br i1 %4, label %fpcall.nil, label %fpcall.next
|
||||
|
||||
fpcall.nil:
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
fpcall.next:
|
||||
%5 = call i32 %3(i8* %0, i8* undef)
|
||||
ret i32 %5
|
||||
}
|
||||
|
||||
; There are two functions with this signature used in a func value. That means
|
||||
; that we'll have to check at runtime which of the two it is (or whether the
|
||||
; func value is nil). This call will thus be lowered to a switch statement.
|
||||
define void @runFunc2(i8*, i32, i8, i8* %context, i8* %parentHandle) {
|
||||
entry:
|
||||
%3 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, i8* @"reflect/types.funcid:func:{basic:uint8}{}", i8* undef, i8* null)
|
||||
%4 = inttoptr i32 %3 to void (i8, i8*, i8*)*
|
||||
%5 = icmp eq void (i8, i8*, i8*)* %4, null
|
||||
br i1 %5, label %fpcall.nil, label %fpcall.next
|
||||
|
||||
fpcall.nil:
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
fpcall.next:
|
||||
call void %4(i8 %2, i8* %0, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Special case for internal/task.start.
|
||||
define void @sleepFuncValue(i8*, i32, i8* nocapture readnone %context, i8* nocapture readnone %parentHandle) {
|
||||
entry:
|
||||
%2 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, i8* @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
|
||||
call void @"internal/task.start"(i32 %2, i8* null, i32 undef, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
101
transform/testdata/func-lowering.out.ll
предоставленный
101
transform/testdata/func-lowering.out.ll
предоставленный
|
@ -1,101 +0,0 @@
|
|||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32-unknown-unknown-wasm"
|
||||
|
||||
declare i32 @runtime.getFuncPtr(i8*, i32, i8*, i8*, i8*)
|
||||
|
||||
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.nilPanic(i8*, i8*)
|
||||
|
||||
declare void @"main$1"(i32, i8*, i8*)
|
||||
|
||||
declare void @"main$2"(i32, i8*, i8*)
|
||||
|
||||
declare void @func1Uint8(i8, i8*, i8*)
|
||||
|
||||
declare void @func2Uint8(i8, i8*, i8*)
|
||||
|
||||
define i32 @runFuncNone(i8* %0, i32 %1, i8* %context, i8* %parentHandle) {
|
||||
entry:
|
||||
br i1 false, label %fpcall.nil, label %fpcall.next
|
||||
|
||||
fpcall.nil: ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
fpcall.next: ; preds = %entry
|
||||
switch i32 %1, label %func.default [
|
||||
i32 0, label %func.nil
|
||||
]
|
||||
|
||||
func.nil: ; preds = %fpcall.next
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
func.next: ; No predecessors!
|
||||
ret i32 undef
|
||||
|
||||
func.default: ; preds = %fpcall.next
|
||||
unreachable
|
||||
}
|
||||
|
||||
define void @runFunc2(i8* %0, i32 %1, i8 %2, i8* %context, i8* %parentHandle) {
|
||||
entry:
|
||||
br i1 false, label %fpcall.nil, label %fpcall.next
|
||||
|
||||
fpcall.nil: ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
fpcall.next: ; preds = %entry
|
||||
switch i32 %1, label %func.default [
|
||||
i32 0, label %func.nil
|
||||
i32 1, label %func.call1
|
||||
i32 2, label %func.call2
|
||||
]
|
||||
|
||||
func.nil: ; preds = %fpcall.next
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
func.call1: ; preds = %fpcall.next
|
||||
call void @func1Uint8(i8 %2, i8* %0, i8* undef)
|
||||
br label %func.next
|
||||
|
||||
func.call2: ; preds = %fpcall.next
|
||||
call void @func2Uint8(i8 %2, i8* %0, i8* undef)
|
||||
br label %func.next
|
||||
|
||||
func.next: ; preds = %func.call2, %func.call1
|
||||
ret void
|
||||
|
||||
func.default: ; preds = %fpcall.next
|
||||
unreachable
|
||||
}
|
||||
|
||||
define void @sleepFuncValue(i8* %0, i32 %1, i8* nocapture readnone %context, i8* nocapture readnone %parentHandle) {
|
||||
entry:
|
||||
switch i32 %1, label %func.default [
|
||||
i32 0, label %func.nil
|
||||
i32 1, label %func.call1
|
||||
i32 2, label %func.call2
|
||||
]
|
||||
|
||||
func.nil: ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
func.call1: ; preds = %entry
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), i8* null, i32 undef, i8* undef, i8* null)
|
||||
br label %func.next
|
||||
|
||||
func.call2: ; preds = %entry
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main$2" to i32), i8* null, i32 undef, i8* undef, i8* null)
|
||||
br label %func.next
|
||||
|
||||
func.next: ; preds = %func.call2, %func.call1
|
||||
ret void
|
||||
|
||||
func.default: ; preds = %entry
|
||||
unreachable
|
||||
}
|
|
@ -146,7 +146,6 @@ func compileGoFileForTesting(t *testing.T, filename string) llvm.Module {
|
|||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
FuncImplementation: config.FuncImplementation(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
Debug: true,
|
||||
}
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче