Description
In the attached code I attempt to do a callback from C to a Swift function (actually a Swift lambda but I suspect the result would be the same. I'll check for completeness).
The _analogReadAsync
function takes a pin and a callback as parameters. When compiling C code that passes a function to this (see the "test" functions), it works. Swift code calling into this fails to execute the callback correctly, with random results.
Looking at the LLVM IR produced by swiftc, it looks OK to my amateur eyes:
define void @_TF3AVR15analogReadAsyncFT3pinVs5UInt88callbackcVs6UInt16T__T_(i8, i8*) #2 {
entry:
%2 = call zeroext i1 @_analogReadAsync(i8 zeroext %0, void (i16)* @_TToFF3AVR15analogReadAsyncFT3pinVs5UInt88callbackcVs6UInt16T__T_U_FS1_T_)
ret void
}
declare zeroext i1 @_analogReadAsync(i8 zeroext, void (i16)*) #1
to set the callback to this function...
define linkonce_odr hidden void @_TToFF3AVR15analogReadAsyncFT3pinVs5UInt88callbackcVs6UInt16T__T_U_FS1_T_(i16 zeroext) #2 {
entry:
call void @_TFF3AVR15analogReadAsyncFT3pinVs5UInt88callbackcVs6UInt16T__T_U_FS1_T_(i16 %0) #3
ret void
}
This is the assembly emitted...
000002fc <_TF3AVR15analogReadAsyncFT3pinVs5UInt88callbackcVs6UInt16T__T_>:
2fc: cf 93 push r28
2fe: df 93 push r29
300: cd b7 in r28, 0x3d ; 61
302: de b7 in r29, 0x3e ; 62
304: 6a e2 ldi r22, 0x2A ; 42
306: 73 e0 ldi r23, 0x03 ; 3
308: 0e 94 8f 05 call 0xb1e ; 0xb1e <_analogReadAsync>
30c: df 91 pop r29
30e: cf 91 pop r28
310: 08 95 ret
0000032a <_TToFF3AVR15analogReadAsyncFT3pinVs5UInt88callbackcVs6UInt16T__T_U_FS1_T_>:
32a: cf 93 push r28
32c: df 93 push r29
32e: cd b7 in r28, 0x3d ; 61
330: de b7 in r29, 0x3e ; 62
332: 0e 94 89 01 call 0x312 ; 0x312 <_TFF3AVR15analogReadAsyncFT3pinVs5UInt88callbackcVs6UInt16T__T_U_FS1_T_>
336: df 91 pop r29
338: cf 91 pop r28
33a: 08 95 ret
At first I couldn't see the problem, r22 and r23 are set to 0x032a, which is the address of the callback function. But when this is subsequently passed around and used for an ICALL later, it all goes wrong. Because ICALL expects a program counter value, not an absolute address. And the program counter refers to 16 bit wide addresses. So we should be loading 0x195 into the program counter, 0x01 into r23 and 0x95 into r22.
Compare this to what avr-gcc does...
This test code:
void _analogReadAsyncTestCallback(unsigned short value) {
_digitalWrite(13, true);
}
void _analogReadAsyncTest(unsigned char pin) {
_analogReadAsync(pin,_analogReadAsyncTestCallback);
}
Creates this assembly:
00000b8c <_analogReadAsyncTest>:
b8c: 61 e2 ldi r22, 0x21 ; 33
b8e: 74 e0 ldi r23, 0x04 ; 4
b90: 0c 94 8f 05 jmp 0xb1e ; 0xb1e <_analogReadAsync>
00000842 <_analogReadAsyncTestCallback>:
842: 61 e0 ldi r22, 0x01 ; 1
844: 8d e0 ldi r24, 0x0D ; 13
846: 0c 94 9c 02 jmp 0x538 ; 0x538 <_digitalWrite>
So here you can see, to get to address 0x842, the compiler creates code to load r22/r23 with the program counter value 0x0421, exactly half the address.