Repository navigation
Phase 4: NuttX 12 stack bounds and Thumb-2 JIT code alignment - #4
Merged
Merged
Conversation
Since NuttX 10.2.0 (upstream commit 2335b69120a) pthread_get_stackaddr_np returns stack_base_ptr, the lowest usable stack address, and pthread_get_stacksize_np the usable size. The NuttX port still assumed the NuttX 7 behaviour (the initial stack pointer at the top) and subtracted the size, so the bounds lay below the real stack and the sanity check in mono_thread_info_get_stack_bounds asserted during GC initialisation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thumb-2 code is only halfword aligned, but the JIT wrote 32-bit instructions and literal words through uint32_t/guint32 pointers and 32-bit bit-field structures, which tell the compiler the address is word aligned. GCC 15 combined two such stores in get_throw_trampoline into an STRD, which takes a UsageFault (UNALIGNED) on a halfword aligned address. In thumb-codegen.h arminstr_t and armword_t are now 2-byte aligned, the 36 32-bit instruction structures are packed with 2-byte alignment (sizes are unchanged), and the code-buffer accesses in thumb-codegen.h, mini-arm.c and tramp-arm.c use arminstr_t. arm-codegen.h (A32, always word aligned) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NevynUK
added a commit
to WildernessLabs/Meadow.OS
that referenced
this pull request
Oct 14, 2026
WildernessLabs/runtime#4 is merged; the tree is the same as the Phase4 commit pinned before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NevynUK
added a commit
that referenced
this pull request
Oct 22, 2026
arm_branch_long() added 2 to the offset when the B.W was at an address that is 2 mod 4. Every caller emits LDR.W rd, [pc, #4]; B.W 8; .word with the literal unpadded straight after the branch, and a branch is relative to the unaligned PC, so the extra 2 landed in the second halfword of the following instruction. (arm_ldr_lit does need the adjustment, as its PC is Align(PC, 4).) For a call from a dynamic method (emit_call_seq) the following instruction is ORR.W ip, ip, #1, whose second halfword 0x0C01 runs as LSRS r1, r0, dotnet#16, so about half of those call sites passed r0 >> 16 as the second argument register. A compiled Regex calls its generated methods with (RegexRunner, ReadOnlySpan<char>), so the span's reference became the top half of the runner's address, and the first match took a MemManage fault (MMFAR 0xc03a) and reset the board. The other callers (GOT and AOT constant loads, sequence point and trigger page loads) had the same exposure. Measured on a Meadow F7: a dynamic method forwarding (object, ReadOnlySpan<char>) to an ordinary method used to deliver the object's address >> 16 as the span reference, and now delivers the right one. Compiled regexes match, YamlDotNet parses, and the ProjLabV3SoakTestRunner app completes a 1000 cycle socket soak test under the JIT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
These two runtime fixes let Mono run a managed Hello World on a Meadow F7 (CCM) under Meadow.OS (NuttX 12), with both the JIT and the interpreter.
Stack bounds (
mono-threads-nuttx.c)Since NuttX 10.2.0 (upstream commit 2335b69120a),
pthread_get_stackaddr_npreturnsstack_base_ptr, the lowest usable address, andpthread_get_stacksize_npreturns the usable size. The NuttX port still assumed the NuttX 7 behaviour, where the address is the initial stack pointer at the top of the stack, so it subtracted the size. The resulting bounds lay below the real stack, andmono_thread_info_get_stack_boundsasserted insgen_client_init:(current > *staddr) && (current < *staddr + *stsize).Thumb-2 code buffer alignment (
thumb-codegen.h,mini-arm.c,tramp-arm.c)Thumb-2 code is only halfword aligned, but the JIT wrote instruction and literal words through 4-byte-aligned types. GCC 15 merged two of these stores in
get_throw_trampolineinto anstrd r1, r2, [r0, #4]withr0=0xc0339bb2. This raised a UsageFault withCFSR=0x01000000(UNALIGNED). STRD, LDRD, STM and LDM always need word alignment, even where a single LDR or STR does not.The fix:
arminstr_tandarmword_tare nowaligned(2).packed, aligned(2). I checked all 69 type sizes before and after with the cross-compiler, and none changed.mini-arm.candtramp-arm.cnow usearminstr_t.arm-codegen.his unchanged.get_throw_trampolinenow uses only SP-relative paired accesses.Testing
On a CCM with Meadow.OS Phase4 (WildernessLabs/Meadow.OS#697):
exit_code=42under the JIT, the JIT with--debug, and--interp.meadow listenshows its stdout and stderr lines.🤖 Generated with Claude Code