transform (func-lowering): remove specializations from function value lowering and fix lowering of a function value of an unimplemented type
Previously, the function value lowering pass had special cases for when there were 0 or 1 function implementations. However, the results of the pass were incorrect in both of these cases. This change removes the specializations and fixes the transformation. In the case that there was a single function implementation, the compiler emitted a select instruction to obtain the function pointer. This selected between null and the implementing function pointer. While this was technically correct, it failed to eliminate indirect function calls. This prevented discovery of these calls by the coroutine lowering pass, and caused async function calls to be passed through unlowered. As a result, the generated code had undefined behavior (usually resulting in a segfault). In the case of no function implementations, the lowering code was correct. However, the lowering code was not run. The discovery of function signatures was accomplished by scanning implementations, and when there were no implementations nothing was discovered or lowered. For maintainability reasons, I have removed both specializations rather than fixing them. This substantially simplifies the code, and reduces the amount of variation that we need to worry about for testing purposes. The IR now generated in the cases of 0 or 1 function implementations can be efficiently simplified by LLVM's optimization passes. Therefore, there should not be a substantial regression in terms of performance or machine code size.
Этот коммит содержится в:
родитель
0a8bfc57ef
коммит
9890c760cf
3 изменённых файлов: 110 добавлений и 121 удалений
|
@ -6,6 +6,7 @@ package transform
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
|
@ -55,17 +56,30 @@ func LowerFuncValues(mod llvm.Module) {
|
||||||
funcValueWithSignaturePtr := llvm.PointerType(mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
|
funcValueWithSignaturePtr := llvm.PointerType(mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
|
||||||
signatures := map[string]*funcSignatureInfo{}
|
signatures := map[string]*funcSignatureInfo{}
|
||||||
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||||
if global.Type() != funcValueWithSignaturePtr {
|
var sig, funcVal llvm.Value
|
||||||
|
switch {
|
||||||
|
case global.Type() == funcValueWithSignaturePtr:
|
||||||
|
sig = llvm.ConstExtractValue(global.Initializer(), []uint32{1})
|
||||||
|
funcVal = global
|
||||||
|
case strings.HasPrefix(global.Name(), "reflect/types.type:func:{"):
|
||||||
|
sig = global
|
||||||
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sig := llvm.ConstExtractValue(global.Initializer(), []uint32{1})
|
|
||||||
name := sig.Name()
|
name := sig.Name()
|
||||||
|
var funcValueWithSignatures []llvm.Value
|
||||||
|
if funcVal.IsNil() {
|
||||||
|
funcValueWithSignatures = []llvm.Value{}
|
||||||
|
} else {
|
||||||
|
funcValueWithSignatures = []llvm.Value{funcVal}
|
||||||
|
}
|
||||||
if info, ok := signatures[name]; ok {
|
if info, ok := signatures[name]; ok {
|
||||||
info.funcValueWithSignatures = append(info.funcValueWithSignatures, global)
|
info.funcValueWithSignatures = append(info.funcValueWithSignatures, funcValueWithSignatures...)
|
||||||
} else {
|
} else {
|
||||||
signatures[name] = &funcSignatureInfo{
|
signatures[name] = &funcSignatureInfo{
|
||||||
sig: sig,
|
sig: sig,
|
||||||
funcValueWithSignatures: []llvm.Value{global},
|
funcValueWithSignatures: funcValueWithSignatures,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,38 +137,8 @@ func LowerFuncValues(mod llvm.Module) {
|
||||||
panic("expected all call uses to be runtime.getFuncPtr")
|
panic("expected all call uses to be runtime.getFuncPtr")
|
||||||
}
|
}
|
||||||
funcID := getFuncPtrCall.Operand(1)
|
funcID := getFuncPtrCall.Operand(1)
|
||||||
switch len(functions) {
|
|
||||||
case 0:
|
// There are functions used in a func value that
|
||||||
// There are no functions used in a func value that implement
|
|
||||||
// this signature. The only possible value is a nil value.
|
|
||||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
|
||||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
|
||||||
panic("expected inttoptr")
|
|
||||||
}
|
|
||||||
nilptr := llvm.ConstPointerNull(inttoptr.Type())
|
|
||||||
inttoptr.ReplaceAllUsesWith(nilptr)
|
|
||||||
inttoptr.EraseFromParentAsInstruction()
|
|
||||||
}
|
|
||||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
|
||||||
case 1:
|
|
||||||
// There is exactly one function with this signature that is
|
|
||||||
// used in a func value. The func value itself can be either nil
|
|
||||||
// or this one function.
|
|
||||||
builder.SetInsertPointBefore(getFuncPtrCall)
|
|
||||||
zero := llvm.ConstInt(uintptrType, 0, false)
|
|
||||||
isnil := builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
|
|
||||||
funcPtrNil := llvm.ConstPointerNull(functions[0].funcPtr.Type())
|
|
||||||
funcPtr := builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
|
|
||||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
|
||||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
|
||||||
panic("expected inttoptr")
|
|
||||||
}
|
|
||||||
inttoptr.ReplaceAllUsesWith(funcPtr)
|
|
||||||
inttoptr.EraseFromParentAsInstruction()
|
|
||||||
}
|
|
||||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
|
||||||
default:
|
|
||||||
// There are multiple functions used in a func value that
|
|
||||||
// implement this signature.
|
// implement this signature.
|
||||||
// What we'll do is transform the following:
|
// What we'll do is transform the following:
|
||||||
// rawPtr := runtime.getFuncPtr(func.ptr)
|
// rawPtr := runtime.getFuncPtr(func.ptr)
|
||||||
|
@ -214,7 +198,6 @@ func LowerFuncValues(mod llvm.Module) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// addFuncLoweringSwitch creates a new switch on a function ID and inserts calls
|
// 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,
|
// to the newly created direct calls. The funcID is the number to switch on,
|
||||||
|
@ -270,13 +253,18 @@ func addFuncLoweringSwitch(mod llvm.Module, builder llvm.Builder, funcID, call l
|
||||||
phiBlocks[i] = bb
|
phiBlocks[i] = bb
|
||||||
phiValues[i] = result
|
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
|
// Create the PHI node so that the call result flows into the
|
||||||
// next block (after the split). This is only necessary when the
|
// next block (after the split). This is only necessary when the
|
||||||
// call produced a value.
|
// call produced a value.
|
||||||
if call.Type().TypeKind() != llvm.VoidTypeKind {
|
|
||||||
builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||||
phi := builder.CreatePHI(call.Type(), "")
|
phi := builder.CreatePHI(call.Type(), "")
|
||||||
phi.AddIncoming(phiValues, phiBlocks)
|
phi.AddIncoming(phiValues, phiBlocks)
|
||||||
call.ReplaceAllUsesWith(phi)
|
call.ReplaceAllUsesWith(phi)
|
||||||
|
} else {
|
||||||
|
// This is always a nil panic, so replace the call result with undef.
|
||||||
|
call.ReplaceAllUsesWith(llvm.Undef(call.Type()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
24
transform/testdata/func-lowering.ll
предоставленный
24
transform/testdata/func-lowering.ll
предоставленный
|
@ -4,10 +4,9 @@ target triple = "wasm32-unknown-unknown-wasm"
|
||||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32 }
|
%runtime.typecodeID = type { %runtime.typecodeID*, i32 }
|
||||||
%runtime.funcValueWithSignature = type { i32, %runtime.typecodeID* }
|
%runtime.funcValueWithSignature = type { i32, %runtime.typecodeID* }
|
||||||
|
|
||||||
@"reflect/types.type:func:{basic:int8}{}" = external constant %runtime.typecodeID
|
|
||||||
@"reflect/types.type:func:{basic:uint8}{}" = external constant %runtime.typecodeID
|
@"reflect/types.type:func:{basic:uint8}{}" = external constant %runtime.typecodeID
|
||||||
@"reflect/types.type:func:{basic:int}{}" = external constant %runtime.typecodeID
|
@"reflect/types.type:func:{basic:int}{}" = external constant %runtime.typecodeID
|
||||||
@"funcInt8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @funcInt8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int8}{}" }
|
@"reflect/types.type:func:{}{basic:uint32}" = external constant %runtime.typecodeID
|
||||||
@"func1Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func1Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
@"func1Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func1Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||||
@"func2Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func2Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
@"func2Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func2Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||||
@"main$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
@"main$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
||||||
|
@ -23,29 +22,26 @@ declare void @"main$1"(i32, i8*, i8*)
|
||||||
|
|
||||||
declare void @"main$2"(i32, i8*, i8*)
|
declare void @"main$2"(i32, i8*, i8*)
|
||||||
|
|
||||||
declare void @funcInt8(i8, i8*, i8*)
|
|
||||||
|
|
||||||
declare void @func1Uint8(i8, i8*, i8*)
|
declare void @func1Uint8(i8, i8*, i8*)
|
||||||
|
|
||||||
declare void @func2Uint8(i8, i8*, i8*)
|
declare void @func2Uint8(i8, i8*, i8*)
|
||||||
|
|
||||||
; Call a function of which only one function with this signature is used as a
|
; There are no functions with this signature used in a func value.
|
||||||
; function value. This means that lowering it to IR is trivial: simply check
|
; This means that this should unconditionally nil panic.
|
||||||
; whether the func value is nil, and if not, call that one function directly.
|
define i32 @runFuncNone(i8*, i32, i8* %context, i8* %parentHandle) {
|
||||||
define void @runFunc1(i8*, i32, i8, i8* %context, i8* %parentHandle) {
|
|
||||||
entry:
|
entry:
|
||||||
%3 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, %runtime.typecodeID* @"reflect/types.type:func:{basic:int8}{}", i8* undef, i8* null)
|
%2 = call i32 @runtime.getFuncPtr(i8* %0, i32 %1, %runtime.typecodeID* @"reflect/types.type:func:{}{basic:uint32}", i8* undef, i8* null)
|
||||||
%4 = inttoptr i32 %3 to void (i8, i8*, i8*)*
|
%3 = inttoptr i32 %2 to i32 (i8*, i8*)*
|
||||||
%5 = icmp eq void (i8, i8*, i8*)* %4, null
|
%4 = icmp eq i32 (i8*, i8*)* %3, null
|
||||||
br i1 %5, label %fpcall.nil, label %fpcall.next
|
br i1 %4, label %fpcall.nil, label %fpcall.next
|
||||||
|
|
||||||
fpcall.nil:
|
fpcall.nil:
|
||||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||||
unreachable
|
unreachable
|
||||||
|
|
||||||
fpcall.next:
|
fpcall.next:
|
||||||
call void %4(i8 %2, i8* %0, i8* undef)
|
%5 = call i32 %3(i8* %0, i8* undef)
|
||||||
ret void
|
ret i32 %5
|
||||||
}
|
}
|
||||||
|
|
||||||
; There are two functions with this signature used in a func value. That means
|
; There are two functions with this signature used in a func value. That means
|
||||||
|
|
27
transform/testdata/func-lowering.out.ll
предоставленный
27
transform/testdata/func-lowering.out.ll
предоставленный
|
@ -4,10 +4,9 @@ target triple = "wasm32-unknown-unknown-wasm"
|
||||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32 }
|
%runtime.typecodeID = type { %runtime.typecodeID*, i32 }
|
||||||
%runtime.funcValueWithSignature = type { i32, %runtime.typecodeID* }
|
%runtime.funcValueWithSignature = type { i32, %runtime.typecodeID* }
|
||||||
|
|
||||||
@"reflect/types.type:func:{basic:int8}{}" = external constant %runtime.typecodeID
|
|
||||||
@"reflect/types.type:func:{basic:uint8}{}" = external constant %runtime.typecodeID
|
@"reflect/types.type:func:{basic:uint8}{}" = external constant %runtime.typecodeID
|
||||||
@"reflect/types.type:func:{basic:int}{}" = external constant %runtime.typecodeID
|
@"reflect/types.type:func:{basic:int}{}" = external constant %runtime.typecodeID
|
||||||
@"funcInt8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @funcInt8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int8}{}" }
|
@"reflect/types.type:func:{}{basic:uint32}" = external constant %runtime.typecodeID
|
||||||
@"func1Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func1Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
@"func1Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func1Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||||
@"func2Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func2Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
@"func2Uint8$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i8, i8*, i8*)* @func2Uint8 to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:uint8}{}" }
|
||||||
@"main$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
@"main$withSignature" = constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main$1" to i32), %runtime.typecodeID* @"reflect/types.type:func:{basic:int}{}" }
|
||||||
|
@ -23,26 +22,32 @@ declare void @"main$1"(i32, i8*, i8*)
|
||||||
|
|
||||||
declare void @"main$2"(i32, i8*, i8*)
|
declare void @"main$2"(i32, i8*, i8*)
|
||||||
|
|
||||||
declare void @funcInt8(i8, i8*, i8*)
|
|
||||||
|
|
||||||
declare void @func1Uint8(i8, i8*, i8*)
|
declare void @func1Uint8(i8, i8*, i8*)
|
||||||
|
|
||||||
declare void @func2Uint8(i8, i8*, i8*)
|
declare void @func2Uint8(i8, i8*, i8*)
|
||||||
|
|
||||||
define void @runFunc1(i8* %0, i32 %1, i8 %2, i8* %context, i8* %parentHandle) {
|
define i32 @runFuncNone(i8* %0, i32 %1, i8* %context, i8* %parentHandle) {
|
||||||
entry:
|
entry:
|
||||||
%3 = icmp eq i32 %1, 0
|
br i1 false, label %fpcall.nil, label %fpcall.next
|
||||||
%4 = select i1 %3, void (i8, i8*, i8*)* null, void (i8, i8*, i8*)* @funcInt8
|
|
||||||
%5 = icmp eq void (i8, i8*, i8*)* %4, null
|
|
||||||
br i1 %5, label %fpcall.nil, label %fpcall.next
|
|
||||||
|
|
||||||
fpcall.nil: ; preds = %entry
|
fpcall.nil: ; preds = %entry
|
||||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||||
unreachable
|
unreachable
|
||||||
|
|
||||||
fpcall.next: ; preds = %entry
|
fpcall.next: ; preds = %entry
|
||||||
call void %4(i8 %2, i8* %0, i8* undef)
|
switch i32 %1, label %func.default [
|
||||||
ret void
|
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) {
|
define void @runFunc2(i8* %0, i32 %1, i8 %2, i8* %context, i8* %parentHandle) {
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче