Skip to content

Commit

Permalink
compiler: pass interface typecode through defer frames
Browse files Browse the repository at this point in the history
Previously, the typecode was passed via a direct reference, which results in invalid IR when the defer is not reached in all return paths.
It also results in incorrect behavior if the defer is in a loop, causing all defers to use the typecode of the last iteration.
  • Loading branch information
niaow authored and aykevl committed Apr 9, 2020
1 parent e077e35 commit 5308e89
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 13 deletions.
12 changes: 8 additions & 4 deletions compiler/defer.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,10 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by the call parameters).
itf := b.getValue(instr.Call.Value) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, receiverValue}
valueTypes = append(valueTypes, b.i8ptrType)
values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
for _, arg := range instr.Call.Args {
val := b.getValue(arg)
values = append(values, val)
Expand Down Expand Up @@ -248,7 +249,7 @@ func (b *builder) createRunDefers() {
}

// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0), b.i8ptrType}
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0), b.uintptrType, b.i8ptrType}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
}
Expand All @@ -264,6 +265,9 @@ func (b *builder) createRunDefers() {
forwardParams = append(forwardParams, forwardParam)
}

// Isolate the typecode.
typecode, forwardParams := forwardParams[0], forwardParams[1:]

// Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms
// with a strict calling convention.
Expand All @@ -272,7 +276,7 @@ func (b *builder) createRunDefers() {
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))

fnPtr, _ := b.getInvokeCall(callback)
fnPtr := b.getInvokePtr(callback, typecode)
b.createCall(fnPtr, forwardParams, "")

case *ir.Function:
Expand Down
22 changes: 13 additions & 9 deletions compiler/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,22 +396,26 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
}
}

// getInvokeCall creates and returns the function pointer and parameters of an
// interface call. It can be used in a call or defer instruction.
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf := b.getValue(instr.Value) // interface

// getInvokePtr creates an interface function pointer lookup for the specified invoke instruction, using a specified typecode.
func (b *builder) getInvokePtr(instr *ssa.CallCommon, typecode llvm.Value) llvm.Value {
llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature))

typecode := b.CreateExtractValue(itf, 0, "invoke.typecode")
values := []llvm.Value{
typecode,
b.getInterfaceMethodSet(instr.Value.Type()),
b.getMethodSignature(instr.Method),
}
fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func")
fnCast := b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
}

// getInvokeCall creates and returns the function pointer and parameters of an
// interface call.
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf := b.getValue(instr.Value) // interface

typecode := b.CreateExtractValue(itf, 0, "invoke.typecode")
fnCast := b.getInvokePtr(instr, typecode)
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")

args := []llvm.Value{receiverValue}
Expand Down
36 changes: 36 additions & 0 deletions testdata/calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ func main() {
thingFunctionalArgs1.Print("functional args 1")
thingFunctionalArgs2 := NewThing(WithName("named thing"))
thingFunctionalArgs2.Print("functional args 2")

// regression testing
regression1033()
}

func runFunc(f func(int), arg int) {
Expand Down Expand Up @@ -108,3 +111,36 @@ func exportedDefer() {
func testBound(f func() string) {
println("bound method:", f())
}

// regression1033 is a regression test for https://github.com/tinygo-org/tinygo/issues/1033.
// In previous versions of the compiler, a deferred call to an interface would create an instruction that did not dominate its uses.
func regression1033() {
foo(&Bar{})
}

type Bar struct {
empty bool
}

func (b *Bar) Close() error {
return nil
}

type Closer interface {
Close() error
}

func foo(bar *Bar) error {
var a int
if !bar.empty {
a = 10
if a != 5 {
return nil
}
}

var c Closer = bar
defer c.Close()

return nil
}

0 comments on commit 5308e89

Please sign in to comment.