Browse Source

compiler: support function pointers outside of addrspace 0

In LLVM 8, the AVR backend has moved all function pointers to address
space 1 by default. Much of the code still assumes function pointers
live in address space 0, leading to assertion failures.

This commit fixes this problem by autodetecting function pointers and
avoiding them in interface pseudo-calls.
pull/218/head
Ayke van Laethem 6 years ago
committed by Ron Evans
parent
commit
c7b91da8c4
  1. 8
      compiler/compiler.go
  2. 34
      compiler/interface-lowering.go
  3. 4
      compiler/interface.go
  4. 6
      src/runtime/interface.go

8
compiler/compiler.go

@ -57,6 +57,7 @@ type Compiler struct {
targetData llvm.TargetData targetData llvm.TargetData
intType llvm.Type intType llvm.Type
i8ptrType llvm.Type // for convenience i8ptrType llvm.Type // for convenience
funcPtrAddrSpace int
uintptrType llvm.Type uintptrType llvm.Type
initFuncs []llvm.Value initFuncs []llvm.Value
interfaceInvokeWrappers []interfaceInvokeWrapper interfaceInvokeWrappers []interfaceInvokeWrapper
@ -125,6 +126,11 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) {
} }
c.i8ptrType = llvm.PointerType(c.ctx.Int8Type(), 0) c.i8ptrType = llvm.PointerType(c.ctx.Int8Type(), 0)
dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false)
dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType)
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
dummyFunc.EraseFromParentAsFunction()
return c, nil return c, nil
} }
@ -462,7 +468,7 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
// {context, funcptr} // {context, funcptr}
paramTypes = append(paramTypes, c.i8ptrType) // context paramTypes = append(paramTypes, c.i8ptrType) // context
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
ptr := llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), 0) ptr := llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace)
ptr = c.ctx.StructType([]llvm.Type{c.i8ptrType, ptr}, false) ptr = c.ctx.StructType([]llvm.Type{c.i8ptrType, ptr}, false)
return ptr, nil return ptr, nil
case *types.Slice: case *types.Slice:

34
compiler/interface-lowering.go

@ -304,7 +304,7 @@ func (p *lowerInterfacesPass) run() {
// interface value should already have returned false. // interface value should already have returned false.
// Replace the function pointer with undef (which will then be // Replace the function pointer with undef (which will then be
// called), indicating to the optimizer this code is unreachable. // called), indicating to the optimizer this code is unreachable.
use.ReplaceAllUsesWith(llvm.Undef(p.i8ptrType)) use.ReplaceAllUsesWith(llvm.Undef(p.uintptrType))
use.EraseFromParentAsInstruction() use.EraseFromParentAsInstruction()
} else if len(itf.types) == 1 { } else if len(itf.types) == 1 {
// There is only one implementation of the given type. // There is only one implementation of the given type.
@ -314,12 +314,12 @@ func (p *lowerInterfacesPass) run() {
// There are multiple types implementing this interface, thus there // There are multiple types implementing this interface, thus there
// are multiple possible functions to call. Delegate calling the // are multiple possible functions to call. Delegate calling the
// right function to a special wrapper function. // right function to a special wrapper function.
bitcasts := getUses(use) inttoptrs := getUses(use)
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() { if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
panic("expected exactly one bitcast use of runtime.interfaceMethod") panic("expected exactly one inttoptr use of runtime.interfaceMethod")
} }
bitcast := bitcasts[0] inttoptr := inttoptrs[0]
calls := getUses(bitcast) calls := getUses(inttoptr)
if len(calls) != 1 || calls[0].IsACallInst().IsNil() { if len(calls) != 1 || calls[0].IsACallInst().IsNil() {
panic("expected exactly one call use of runtime.interfaceMethod") panic("expected exactly one call use of runtime.interfaceMethod")
} }
@ -340,14 +340,14 @@ func (p *lowerInterfacesPass) run() {
// call, after selecting the right concrete type. // call, after selecting the right concrete type.
redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes) redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
// Replace the old lookup/bitcast/call with the new call. // Replace the old lookup/inttoptr/call with the new call.
p.builder.SetInsertPointBefore(call) p.builder.SetInsertPointBefore(call)
retval := p.builder.CreateCall(redirector, params, "") retval := p.builder.CreateCall(redirector, params, "")
if retval.Type().TypeKind() != llvm.VoidTypeKind { if retval.Type().TypeKind() != llvm.VoidTypeKind {
call.ReplaceAllUsesWith(retval) call.ReplaceAllUsesWith(retval)
} }
call.EraseFromParentAsInstruction() call.EraseFromParentAsInstruction()
bitcast.EraseFromParentAsInstruction() inttoptr.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction() use.EraseFromParentAsInstruction()
} }
} }
@ -542,22 +542,22 @@ func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
return p.signatures[name] return p.signatures[name]
} }
// replaceInvokeWithCall replaces a runtime.interfaceMethod + bitcast with a // replaceInvokeWithCall replaces a runtime.interfaceMethod + inttoptr with a
// concrete method. This can be done when only one type implements the // concrete method. This can be done when only one type implements the
// interface. // interface.
func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInfo, signature *signatureInfo) { func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInfo, signature *signatureInfo) {
bitcasts := getUses(use) inttoptrs := getUses(use)
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() { if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
panic("expected exactly one bitcast use of runtime.interfaceMethod") panic("expected exactly one inttoptr use of runtime.interfaceMethod")
} }
bitcast := bitcasts[0] inttoptr := inttoptrs[0]
function := typ.getMethod(signature).function function := typ.getMethod(signature).function
if bitcast.Type() != function.Type() { if inttoptr.Type() != function.Type() {
p.builder.SetInsertPointBefore(use) p.builder.SetInsertPointBefore(use)
function = p.builder.CreateBitCast(function, bitcast.Type(), "") function = p.builder.CreateBitCast(function, inttoptr.Type(), "")
} }
bitcast.ReplaceAllUsesWith(function) inttoptr.ReplaceAllUsesWith(function)
bitcast.EraseFromParentAsInstruction() inttoptr.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction() use.EraseFromParentAsInstruction()
} }

4
compiler/interface.go

@ -203,7 +203,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
} }
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{ methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal, signatureGlobal,
llvm.ConstBitCast(fn, c.i8ptrType), llvm.ConstPtrToInt(fn, c.uintptrType),
}) })
methods[i] = methodInfo methods[i] = methodInfo
} }
@ -400,7 +400,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
c.getMethodSignature(instr.Method), c.getMethodSignature(instr.Method),
} }
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func") fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast") fnCast := c.builder.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver") receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
args := []llvm.Value{receiverValue} args := []llvm.Value{receiverValue}

6
src/runtime/interface.go

@ -39,8 +39,8 @@ func interfaceTypeAssert(ok bool) {
// See compiler/interface-lowering.go for details. // See compiler/interface-lowering.go for details.
type interfaceMethodInfo struct { type interfaceMethodInfo struct {
signature *uint8 // external *i8 with a name identifying the Go function signature signature *uint8 // external *i8 with a name identifying the Go function signature
funcptr *uint8 // bitcast from the actual function pointer funcptr uintptr // bitcast from the actual function pointer
} }
// Pseudo function call used while putting a concrete value in an interface, // Pseudo function call used while putting a concrete value in an interface,
@ -59,4 +59,4 @@ func interfaceImplements(typecode uintptr, interfaceMethodSet **uint8) bool
// Pseudo function that returns a function pointer to the method to call. // Pseudo function that returns a function pointer to the method to call.
// See the interface lowering pass for how this is lowered to a real call. // See the interface lowering pass for how this is lowered to a real call.
func interfaceMethod(typecode uintptr, interfaceMethodSet **uint8, signature *uint8) *uint8 func interfaceMethod(typecode uintptr, interfaceMethodSet **uint8, signature *uint8) uintptr

Loading…
Cancel
Save