Skip to content

Commit 5308e89

Browse files
niaowaykevl
authored andcommitted
compiler: pass interface typecode through defer frames
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.
1 parent e077e35 commit 5308e89

File tree

3 files changed

+57
-13
lines changed

3 files changed

+57
-13
lines changed

compiler/defer.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,10 @@ func (b *builder) createDefer(instr *ssa.Defer) {
9292
// Collect all values to be put in the struct (starting with
9393
// runtime._defer fields, followed by the call parameters).
9494
itf := b.getValue(instr.Call.Value) // interface
95+
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
9596
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
96-
values = []llvm.Value{callback, next, receiverValue}
97-
valueTypes = append(valueTypes, b.i8ptrType)
97+
values = []llvm.Value{callback, next, typecode, receiverValue}
98+
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
9899
for _, arg := range instr.Call.Args {
99100
val := b.getValue(arg)
100101
values = append(values, val)
@@ -248,7 +249,7 @@ func (b *builder) createRunDefers() {
248249
}
249250

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

268+
// Isolate the typecode.
269+
typecode, forwardParams := forwardParams[0], forwardParams[1:]
270+
267271
// Add the context parameter. An interface call cannot also be a
268272
// closure but we have to supply the parameter anyway for platforms
269273
// with a strict calling convention.
@@ -272,7 +276,7 @@ func (b *builder) createRunDefers() {
272276
// Parent coroutine handle.
273277
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
274278

275-
fnPtr, _ := b.getInvokeCall(callback)
279+
fnPtr := b.getInvokePtr(callback, typecode)
276280
b.createCall(fnPtr, forwardParams, "")
277281

278282
case *ir.Function:

compiler/interface.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -396,22 +396,26 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
396396
}
397397
}
398398

399-
// getInvokeCall creates and returns the function pointer and parameters of an
400-
// interface call. It can be used in a call or defer instruction.
401-
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
402-
// Call an interface method with dynamic dispatch.
403-
itf := b.getValue(instr.Value) // interface
404-
399+
// getInvokePtr creates an interface function pointer lookup for the specified invoke instruction, using a specified typecode.
400+
func (b *builder) getInvokePtr(instr *ssa.CallCommon, typecode llvm.Value) llvm.Value {
405401
llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature))
406-
407-
typecode := b.CreateExtractValue(itf, 0, "invoke.typecode")
408402
values := []llvm.Value{
409403
typecode,
410404
b.getInterfaceMethodSet(instr.Value.Type()),
411405
b.getMethodSignature(instr.Method),
412406
}
413407
fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func")
414-
fnCast := b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
408+
return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
409+
}
410+
411+
// getInvokeCall creates and returns the function pointer and parameters of an
412+
// interface call.
413+
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
414+
// Call an interface method with dynamic dispatch.
415+
itf := b.getValue(instr.Value) // interface
416+
417+
typecode := b.CreateExtractValue(itf, 0, "invoke.typecode")
418+
fnCast := b.getInvokePtr(instr, typecode)
415419
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
416420

417421
args := []llvm.Value{receiverValue}

testdata/calls.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ func main() {
6161
thingFunctionalArgs1.Print("functional args 1")
6262
thingFunctionalArgs2 := NewThing(WithName("named thing"))
6363
thingFunctionalArgs2.Print("functional args 2")
64+
65+
// regression testing
66+
regression1033()
6467
}
6568

6669
func runFunc(f func(int), arg int) {
@@ -108,3 +111,36 @@ func exportedDefer() {
108111
func testBound(f func() string) {
109112
println("bound method:", f())
110113
}
114+
115+
// regression1033 is a regression test for https://github.com/tinygo-org/tinygo/issues/1033.
116+
// In previous versions of the compiler, a deferred call to an interface would create an instruction that did not dominate its uses.
117+
func regression1033() {
118+
foo(&Bar{})
119+
}
120+
121+
type Bar struct {
122+
empty bool
123+
}
124+
125+
func (b *Bar) Close() error {
126+
return nil
127+
}
128+
129+
type Closer interface {
130+
Close() error
131+
}
132+
133+
func foo(bar *Bar) error {
134+
var a int
135+
if !bar.empty {
136+
a = 10
137+
if a != 5 {
138+
return nil
139+
}
140+
}
141+
142+
var c Closer = bar
143+
defer c.Close()
144+
145+
return nil
146+
}

0 commit comments

Comments
 (0)