compiler: fix interface calls for big underlying values

When the underlying value of an interface does not fit in a pointer, a
pointer to the value was correctly inserted in the heap. However, the
receiving method still assumed it got the underlying value instead of a
pointer to it leading to a crash.

This commit inserts wrapper functions for method calls on interfaces.

The bug wasn't obvious as on a 64-bit system, the underlying value was
almost always put directly in the interface. However, it led to a crash
on the AVR platform where pointer are (usually) just 16 bits making it
far more likely that underlying values cannot be directly stored in an
interface.
This commit is contained in:
Ayke van Laethem
2018-10-07 02:06:48 +02:00
parent 482c5633dd
commit 4957db89f4
3 changed files with 156 additions and 8 deletions
+56 -8
View File
@@ -419,8 +419,20 @@ func (c *Compiler) Compile(mainPath string) error {
if f.LLVMFn.IsNil() {
return errors.New("cannot find function: " + f.LinkName())
}
fn := llvm.ConstBitCast(f.LLVMFn, c.i8ptrType)
funcPointers = append(funcPointers, fn)
fn := f.LLVMFn
receiverType := fn.Type().ElementType().ParamTypes()[0]
if c.targetData.TypeAllocSize(receiverType) > c.targetData.TypeAllocSize(c.i8ptrType) {
// The receiver value doesn't fit in a pointer. This means that
// the interface contains a pointer to the receiver value
// instead of the value itself. Which means we have to create a
// wrapper function.
fn, err = c.wrapInterfaceInvoke(f)
if err != nil {
return err
}
}
fnPtr := llvm.ConstBitCast(fn, c.i8ptrType)
funcPointers = append(funcPointers, fnPtr)
signatureNum := c.ir.MethodNum(method.Obj().(*types.Func))
signature := llvm.ConstInt(llvm.Int16Type(), uint64(signatureNum), false)
signatures = append(signatures, signature)
@@ -754,6 +766,42 @@ func (c *Compiler) getDIType(typ types.Type) (llvm.Metadata, error) {
}
}
// Wrap an interface method function pointer. The wrapper takes in a pointer to
// the underlying value, dereferences it, and calls the real method. This
// wrapper is only needed when the interface value actually doesn't fit in a
// pointer and a pointer to the value must be created.
func (c *Compiler) wrapInterfaceInvoke(f *ir.Function) (llvm.Value, error) {
fnType := f.LLVMFn.Type().ElementType()
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[1:]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper := llvm.AddFunction(c.mod, f.LinkName()+"$invoke", wrapFnType)
wrapper.SetLinkage(llvm.InternalLinkage)
pos := c.ir.Program.Fset.Position(f.Pos())
difunc, err := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
if err != nil {
return llvm.Value{}, err
}
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
block := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(block)
receiverType := fnType.ParamTypes()[0]
receiverPtrType := llvm.PointerType(receiverType, 0)
receiverPtr := c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr")
receiver := c.builder.CreateLoad(receiverPtr, "receiver")
params := append([]llvm.Value{receiver}, wrapper.Params()[1:]...)
if fnType.ReturnType().TypeKind() == llvm.VoidTypeKind {
c.builder.CreateCall(f.LLVMFn, params, "")
c.builder.CreateRetVoid()
} else {
ret := c.builder.CreateCall(f.LLVMFn, params, "ret")
c.builder.CreateRet(ret)
}
return wrapper, nil
}
func (c *Compiler) parseFuncDecl(f *ir.Function) (*Frame, error) {
frame := &Frame{
fn: f,
@@ -816,7 +864,7 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) (*Frame, error) {
}
if c.Debug && f.Synthetic == "package initializer" {
difunc, err := c.attachDebugInfoRaw(f, "", 0)
difunc, err := c.attachDebugInfoRaw(f, f.LLVMFn, "", "", 0)
if err != nil {
return nil, err
}
@@ -835,10 +883,10 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) (*Frame, error) {
func (c *Compiler) attachDebugInfo(f *ir.Function) (llvm.Metadata, error) {
pos := c.ir.Program.Fset.Position(f.Syntax().Pos())
return c.attachDebugInfoRaw(f, pos.Filename, pos.Line)
return c.attachDebugInfoRaw(f, f.LLVMFn, "", pos.Filename, pos.Line)
}
func (c *Compiler) attachDebugInfoRaw(f *ir.Function, filename string, line int) (llvm.Metadata, error) {
func (c *Compiler) attachDebugInfoRaw(f *ir.Function, llvmFn llvm.Value, suffix, filename string, line int) (llvm.Metadata, error) {
if _, ok := c.difiles[filename]; !ok {
dir, file := filepath.Split(filename)
if dir != "" {
@@ -862,8 +910,8 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, filename string, line int)
Flags: 0, // ?
})
difunc := c.dibuilder.CreateFunction(c.difiles[filename], llvm.DIFunction{
Name: f.RelString(nil),
LinkageName: f.LinkName(),
Name: f.RelString(nil) + suffix,
LinkageName: f.LinkName() + suffix,
File: c.difiles[filename],
Line: line,
Type: diFuncType,
@@ -873,7 +921,7 @@ func (c *Compiler) attachDebugInfoRaw(f *ir.Function, filename string, line int)
Flags: llvm.FlagPrototyped,
Optimized: true,
})
f.LLVMFn.SetSubprogram(difunc)
llvmFn.SetSubprogram(difunc)
return difunc, nil
}
+87
View File
@@ -0,0 +1,87 @@
package main
func main() {
thing := &Thing{"foo"}
println("thing:", thing.String())
thing.Print()
printItf(5)
printItf(byte('x'))
printItf("foo")
printItf(*thing)
printItf(thing)
printItf(Stringer(thing))
printItf(Number(3))
array := Array([4]uint32{1, 7, 11, 13})
printItf(array)
s := Stringer(thing)
println("Stringer.String():", s.String())
var itf interface{} = s
println("Stringer.(*Thing).String():", itf.(Stringer).String())
}
func printItf(val interface{}) {
switch val := val.(type) {
case Doubler:
println("is Doubler:", val.Double())
case Tuple:
println("is Tuple:", val.Nth(0), val.Nth(1), val.Nth(2), val.Nth(3))
val.Print()
case int:
println("is int:", val)
case byte:
println("is byte:", val)
case string:
println("is string:", val)
case Thing:
println("is Thing:", val.String())
case *Thing:
println("is *Thing:", val.String())
case Foo:
println("is Foo:", val)
default:
println("is ?")
}
}
type Thing struct {
name string
}
func (t Thing) String() string {
return t.name
}
func (t Thing) Print() {
println("Thing.Print:", t.name)
}
type Stringer interface {
String() string
}
type Foo int
type Number int
func (n Number) Double() int {
return int(n) * 2
}
type Doubler interface {
Double() int
}
type Tuple interface {
Nth(int) uint32
Print()
}
type Array [4]uint32
func (a Array) Nth(n int) uint32 {
return a[n]
}
func (a Array) Print() {
println("Array len:", len(a))
}
+13
View File
@@ -0,0 +1,13 @@
thing: foo
Thing.Print: foo
is int: 5
is byte: 120
is string: foo
is Thing: foo
is *Thing: foo
is *Thing: foo
is Doubler: 6
is Tuple: 1 7 11 13
Array len: 4
Stringer.String(): foo
Stringer.(*Thing).String(): foo