You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
RV32 native emitter fails compiling some of the tests #15551
Thanks for bringing this to my attention, I'll look into this right away.
I guess there are more spots where having code sequences of variable length (as in, using RVC opcodes or performing some simple optimisations) is an issue. If there's a way for the emitter to detect it's running under mpy-cross at compile time I can attempt to have longer sequences only in that case, otherwise I'll have less efficient code also when generating code at runtime.
There are actually two issues here. The first one is mpy-cross not handling variable length sequences in asm_rv32_emit_call_ind, asm_rv32_emit_jump_if_reg_eq, asm_rv32_emit_jump_if_reg_nonzero, and asm_rv32_emit_jump. I thought that was not a problem?
Once that's sorted out, then the generated code for those tests crashes mid-way on device, and I'm analysing what's been produced by the emitter. The code looks OK but I need to spend more time with that to bisect where things go wrong. However, I'm looking at the output of basics/array1.py and I notice there are a lot of dead loads to REG_ARG2, both from registers and locals (see attached image). Since I don't recall that happening when I was debugging the emitter the last time before commit, is that normal? (because if it's not then I messed something along the way, which makes it easier for me to check I guess :)) Edit: no, it was my Ghidra analysis script that didn't pick that argument up - sorry.
If there's a way for the emitter to detect it's running under mpy-cross at compile time I can attempt to have longer sequences only in that case
In principle there should be no difference when mpy-cross generates code compared to generating it on-device. So I suspect this code generation issue would also occur on-device, but I didn't test that yet.
The problem is because the RV32 assembler is emitting short jumps when it shouldn't be. It can only emit short jumps (jumps encoded in fewer bytes than would be by an arbitrarily large jump) when it's a backwards jump, because then the offset is known on the first pass and the code size doesn't change.
See eg py/asmthumb.c, eg:
mp_uint_tdest=get_label_dest(as, label);
mp_int_trel=dest-as->base.code_offset;
rel-=4; // account for instruction prefetch, PC is 4 bytes ahead of this instructionif (dest!= (mp_uint_t)-1&&rel <= -4) {
// is a backwards jump, so we know the size of the jump on the first pass// calculate rel assuming 9 bit relative jumpif (SIGNED_FIT9(rel)) {
asm_thumb_op16(as, OP_BCC_N(cond, rel));
return;
}
}
... otherwiseencodealargejump
and that fails in the console with: TypeError: can't convert '' object to str implicitly - as in the first argument to array.array is invalid. I'm not able to reproduce this with the x64 emitter so I assume it's most probable due to some issue in my code to begin with.
I've removed all sorts of optimisations and variable code length generation to have something stable and to reduce the error surface, and I cannot easily see the issue at hand. The generated code seems fine on the surface, but now I'm deep into the method call infrastructure inside the runtime as args[0] passed to array_make_new seems to be of the wrong type and point to an invalid value:
A4 gets filled in at 0x420138de by dereferencing the args[0] passed to array_make_new. The comparison with mp_obj_str_binary_op fails and bad_implicit_conversion gets invoked.
I'm still looking at that, apologies for delays in updates. I'm not yet familiar with this part of MicroPython's internals.
Right now I've minimised basics/array1.py' to: ... and that fails in the console with: TypeError: can't convert '' object to str implicitly- as in the first argument toarray.array` is invalid. I'm not able to reproduce this with the x64 emitter so I assume it's most probable due to some issue in my code to begin with.
I had a look at this and it's actually a bug in the native emitter.
But the bug only surfaces for emitters where REG_RET is different to REG_TEMP0. The RV32 emitter looks like it's the only emitter where that is the case, hence why we see it here.
Thanks! With #15573 applied now the basics test suite passes when ran through mpy-cross except for frozenset_binop, set_binop, and string_splitlines.
mpy-cross didn't trigger any assertions so it could be something similar to #15573 or I might have messed up somewhere else. I'll let you know!
Edit: the C.J opcode had an off-by-one error in its address range check, now fixed. Now failures are extmod/vfs_fat_ilistdir_del, float/string_format2, unicode/file1, unicode/file2, unicode/file_invalid, and anything past thread/disable_irq in the thread suite.
extmod/vfs_fat_ilistdir_del fails only if extmod/socket_tcp_basic or extmod/socket_udp_nonblock and extmod/vfs_fat_finaliser tests were run before. To repro, ./run-tests.py --target esp32 --device /dev/ttyUSB0 --via-mpy --emit native --mpy-cross-flags="-march=rv32imc" -d extmod -i "socket_.*" -i "vfs_fat_finaliser.*" -i "vfs_fat_ilistdir.*" would be enough.
The test in question actually succeeds and afterOK is being sent to the serial port and things are cleaned up the board crashes: The test crashes when setting up the code state (the OK string comes from the previous test).
For float/string_format2 things are even more interesting. In the code block invoked through the second call to fun_native_call, the pointer to mp_setup_code_state ends up pointing to some random area with invalid opcodes.
Regarding float/string_format2 - either I got the C.LW opcode wrong or something else is fishy.
fun_native_call gets invoked twice, the first time it merely sets up the code state and does nothing else:
=> 0x403ddf64: addi sp,sp,-48 ; Allocate stack space
0x403ddf68: sw ra,0(sp) ; Save changed registers
0x403ddf6a: sw s0,4(sp) ;
0x403ddf6c: sw s1,8(sp) ;
0x403ddf6e: sw s3,12(sp) ;
0x403ddf70: sw s4,16(sp) ;
0x403ddf72: sw s5,20(sp) ;
0x403ddf74: lw s1,4(a0) ; Lookup the address of the function table...
0x403ddf76: lw s1,12(s1) ; ...
0x403ddf78: lw s1,0(s1) ; S1 points to mp_fun_table
0x403ddf7a: sw a0,24(sp) ; Save the first argument into "local_a"
0x403ddf7c: li a0,1 ;
0x403ddf7e: sw a0,36(sp) ; Store 1 into another local
0x403ddf80: addi a0,sp,24 ; Load the address of "local_a" (code_state argument)
0x403ddf82: lw s0,180(s1) ; S0 points to mp_setup_code_state_native
0x403ddf86: jalr s0 ; Call mp_setup_code_state_native
0x403ddf88: lw a0,0(s1) ; A0 points to const_none
0x403ddf8a: auipc s0,0x0 ; Load PC
0x403ddf8e: jr 8(s0) ; Jump to next instruction
0x403ddf92: lw ra,0(sp) ; Restore registers
0x403ddf94: lw s0,4(sp) ;
0x403ddf96: lw s1,8(sp) ;
0x403ddf98: lw s3,12(sp) ;
0x403ddf9a: lw s4,16(sp) ;
0x403ddf9c: lw s5,20(sp) ;
0x403ddf9e: addi sp,sp,48 ; Reclaim stack space
0x403ddfa2: ret ; Done
The code sequence until the call to mp_setup_code_state_native is pretty much the same for every native call. Now, the second invocation of fun_native_call goes as follows:
=> 0x403a3734: addi sp,sp,-340 ; Allocate stack space
0x403a3738: sw ra,0(sp) ; Save changed registers
0x403a373a: sw s0,4(sp) ;
0x403a373c: sw s1,8(sp) ;
0x403a373e: sw s3,12(sp) ;
0x403a3740: sw s4,16(sp) ;
0x403a3742: sw s5,20(sp) ;
0x403a3744: lw s1,4(a0) ; Lookup the address of the function table
0x403a3746: lw s5,8(s1) ;
0x403a374a: lw s1,12(s1) ;
0x403a374c: lw s1,80(s1) ; S1 points somewhere else than mp_fun_table...
0x403a374e: sw a0,96(sp) ;
0x403a3750: li a0,56 ;
0x403a3754: sw a0,108(sp) ;
0x403a3756: addi a0,sp,96 ;
0x403a3758: lw s0,180(s1) ; ...that is still accessed as if it were mp_fun_table
0x403a375c: jalr s0 ; and here be dragons
...
@dpgeorge Can this specific test failure be an issue with the native emitter infrastructure rather than with the RV32 code generator?
With ./run-tests.py --target esp32 --device /dev/ttyUSB0 --via-mpy --emit native --mpy-cross-flags="-march=rv32imc" -d extmod -i "socket_.*" -i "vfs_fat_finaliser.*" -i "vfs_fat_ilistdir.*" I see the bytecode buffer containing potentially incorrect data, as the code attempts to index a table pointer with a very large index (0x3FCDC724), and ultimately reading from unmapped memory (just a bit before 0x3FC80000, which is the beginning of the internal memory block).
that triggers a FAT file finaliser to run, the file left over from vfs_fat_finaliser.py that wasn't explicitly closed
that triggers (via the FAT driver closing the file and flushing its data) native code to be run from RAMBlockDevice
native code memory is already freed (see first step), so it crashes
To fix that... probably native_code_pointers needs to be a linked list (not using GC heap) and then this link list freed after gc_sweep_all() has been called.
To fix that... probably native_code_pointers needs to be a linked list (not using GC heap) and then this link list freed after gc_sweep_all() has been called.
@projectgus Do you have a list of the tests that failed due to #15423?
I'm seeing similar issues when running most thread tests through the native emitter and I was wondering if maybe that bug is still showing up somehow. I've looked at the generated code and there's nothing obviously wrong on my end.
I can reproduce the thread test issues also on a ESP32-D0WD-V3 using ./run-tests.py --target esp32 --device /dev/ttyUSB0 --via-mpy --emit native --mpy-cross-flags="-march=xtensawin" -d thread and the firmware built from the master branch for ESP32_GENERIC...
Activity
dpgeorge commentedon Jul 26, 2024
@agatti you may be interested to look into this issue.
agatti commentedon Jul 26, 2024
Thanks for bringing this to my attention, I'll look into this right away.
I guess there are more spots where having code sequences of variable length (as in, using RVC opcodes or performing some simple optimisations) is an issue. If there's a way for the emitter to detect it's running under mpy-cross at compile time I can attempt to have longer sequences only in that case, otherwise I'll have less efficient code also when generating code at runtime.
agatti commentedon Jul 26, 2024
There are actually two issues here. The first one is mpy-cross not handling variable length sequences in
asm_rv32_emit_call_ind,asm_rv32_emit_jump_if_reg_eq,asm_rv32_emit_jump_if_reg_nonzero, andasm_rv32_emit_jump. I thought that was not a problem?Once that's sorted out, then the generated code for those tests crashes mid-way on device, and I'm analysing what's been produced by the emitter. The code looks OK but I need to spend more time with that to bisect where things go wrong.
However, I'm looking at the output ofEdit: no, it was my Ghidra analysis script that didn't pick that argument up - sorry.basics/array1.pyand I notice there are a lot of dead loads to REG_ARG2, both from registers and locals (see attached image). Since I don't recall that happening when I was debugging the emitter the last time before commit, is that normal? (because if it's not then I messed something along the way, which makes it easier for me to check I guess :))dpgeorge commentedon Jul 28, 2024
In principle there should be no difference when mpy-cross generates code compared to generating it on-device. So I suspect this code generation issue would also occur on-device, but I didn't test that yet.
dpgeorge commentedon Jul 28, 2024
The problem is because the RV32 assembler is emitting short jumps when it shouldn't be. It can only emit short jumps (jumps encoded in fewer bytes than would be by an arbitrarily large jump) when it's a backwards jump, because then the offset is known on the first pass and the code size doesn't change.
See eg
py/asmthumb.c, eg:agatti commentedon Jul 30, 2024
Thanks for the clarification, I'll adapt the code accordingly after I fix the other issues.
Right now I've minimised `basics/array1.py' to:
and that fails in the console with:
TypeError: can't convert '' object to str implicitly- as in the first argument toarray.arrayis invalid. I'm not able to reproduce this with the x64 emitter so I assume it's most probable due to some issue in my code to begin with.I've removed all sorts of optimisations and variable code length generation to have something stable and to reduce the error surface, and I cannot easily see the issue at hand. The generated code seems fine on the surface, but now I'm deep into the method call infrastructure inside the runtime as
args[0]passed toarray_make_newseems to be of the wrong type and point to an invalid value:A4 gets filled in at 0x420138de by dereferencing the
args[0]passed toarray_make_new. The comparison withmp_obj_str_binary_opfails andbad_implicit_conversiongets invoked.I'm still looking at that, apologies for delays in updates. I'm not yet familiar with this part of MicroPython's internals.
dpgeorge commentedon Jul 30, 2024
I had a look at this and it's actually a bug in the native emitter.
But the bug only surfaces for emitters where
REG_RETis different toREG_TEMP0. The RV32 emitter looks like it's the only emitter where that is the case, hence why we see it here.I'll fix it.
dpgeorge commentedon Jul 31, 2024
See #15573 for a fix for the native emitter. The above
array.arraytest should now work.agatti commentedon Jul 31, 2024
Thanks! With #15573 applied now the
basicstest suite passes when ran through mpy-cross except forfrozenset_binop,set_binop, andstring_splitlines.mpy-crossdidn't trigger any assertions so it could be something similar to #15573 or I might have messed up somewhere else. I'll let you know!Edit: the
C.Jopcode had an off-by-one error in its address range check, now fixed. Now failures areextmod/vfs_fat_ilistdir_del,float/string_format2,unicode/file1,unicode/file2,unicode/file_invalid, and anything pastthread/disable_irqin thethreadsuite.agatti commentedon Jul 31, 2024
unicode/file1fails withunicode/file2andunicode/file_invalidfail withCould this be something similar to #15573?
agatti commentedon Jul 31, 2024
extmod/vfs_fat_ilistdir_delfails only ifextmod/socket_tcp_basicorextmod/socket_udp_nonblockandextmod/vfs_fat_finalisertests were run before. To repro,./run-tests.py --target esp32 --device /dev/ttyUSB0 --via-mpy --emit native --mpy-cross-flags="-march=rv32imc" -d extmod -i "socket_.*" -i "vfs_fat_finaliser.*" -i "vfs_fat_ilistdir.*"would be enough.The test in question actually succeeds and afterThe test crashes when setting up the code state (theOKis being sent to the serial port and things are cleaned up the board crashes:OKstring comes from the previous test).agatti commentedon Jul 31, 2024
For
float/string_format2things are even more interesting. In the code block invoked through the second call tofun_native_call, the pointer tomp_setup_code_stateends up pointing to some random area with invalid opcodes.agatti commentedon Aug 1, 2024
Regarding
float/string_format2- either I got theC.LWopcode wrong or something else is fishy.fun_native_callgets invoked twice, the first time it merely sets up the code state and does nothing else:The code sequence until the call to mp_setup_code_state_native is pretty much the same for every native call. Now, the second invocation of
fun_native_callgoes as follows:Edit: fixed in ba9d582
agatti commentedon Aug 1, 2024
extmod/vfs_fat_ilistdir_delfails due to a NULL pointer when setting up the code state:Getting up to that point looks fine:
agatti commentedon Aug 2, 2024
The
threadtests that fail (except for one) actually deadlock, so I suspect that memory ordering may play a role here.Anyway, the state so far:
Fixed by esp32/main: Store native code as linked list instead of list on GC heap. #15589.extmod/vfs_fat_ilistdir_delcrashes because mp_setup_code_state_helper receives a code_state parameter with NULLmp_code_state_t->ip.thread/mutate_{byteordering,dict,instance,list,set}deadlock.thread/stress_aescompletes but does not emit thedonestring.thread/stress_{recurse,schedule}deadlock.thread/thread_{coop,exc1}deadlock.thread/thread_exc2completes but does not print the traceback.thread/thread_{gc1,ident1,lock3,shared1,shared2,sleep1,sleep2,stacksize1}deadlock.unicode/{file1,file2,file_invalid}pass a non-method object tomp_load_method.agatti commentedon Aug 2, 2024
@dpgeorge Can this specific test failure be an issue with the native emitter infrastructure rather than with the RV32 code generator?
With
./run-tests.py --target esp32 --device /dev/ttyUSB0 --via-mpy --emit native --mpy-cross-flags="-march=rv32imc" -d extmod -i "socket_.*" -i "vfs_fat_finaliser.*" -i "vfs_fat_ilistdir.*"I see the bytecode buffer containing potentially incorrect data, as the code attempts to index a table pointer with a very large index (0x3FCDC724), and ultimately reading from unmapped memory (just a bit before 0x3FC80000, which is the beginning of the internal memory block).dpgeorge commentedon Aug 2, 2024
Hmm, it looks like there's an issue with
vfs_fat_finaliser.py, because running that multiple times in a row leads to issues. Eg:I think it's because of:
native_code_pointersgc_sweep_all()vfs_fat_finaliser.pythat wasn't explicitly closedRAMBlockDeviceTo fix that... probably
native_code_pointersneeds to be a linked list (not using GC heap) and then this link list freed aftergc_sweep_all()has been called.agatti commentedon Aug 2, 2024
Right. I'll focus on the deadlocks then and leave that failure alone as it's not specific to the code generator, thanks!
dpgeorge commentedon Aug 3, 2024
I've done that in #15589.
agatti commentedon Aug 10, 2024
@projectgus Do you have a list of the tests that failed due to #15423?
I'm seeing similar issues when running most
threadtests through the native emitter and I was wondering if maybe that bug is still showing up somehow. I've looked at the generated code and there's nothing obviously wrong on my end.agatti commentedon Aug 11, 2024
I can reproduce the
threadtest issues also on a ESP32-D0WD-V3 using./run-tests.py --target esp32 --device /dev/ttyUSB0 --via-mpy --emit native --mpy-cross-flags="-march=xtensawin" -d threadand the firmware built from the master branch forESP32_GENERIC...agatti commentedon Aug 11, 2024
...same goes for the three
unicodefailing tests. I'll clean up #15575 and then I guess I can't do much more about this on the RV32 side.dpgeorge commentedon Aug 19, 2024
Thanks @agatti for working on this issue. Testing as of 7d8b2d8 on an RP2350-RISCV I get the following:
So this issue can be closed.