nx_emu_cortexm.nx
buildroot/runtime/nx_emu_cortexm.nx
about
nx_emu_cortexm.nx -- sovereign ARMv7-M / Cortex-M (Thumb-2) interpreter (NX-EMU).
Little-endian, MIXED 16/32-bit instructions (32-bit iff hw1[15:11] in {11101,11110,
11111} i.e. hw1>=0xE800), 16 GPRs + NZCV. Bare-metal: BKPT halts with result=r0
(no OS). NO qemu. license_tier: ORIGINAL
2026-09-03, ISA LANE. WHAT THIS REVISION ADDS AND WHY.
The first cut decoded exactly the subset nxc2 cortex_m backend emits: push/pop,
mov(hi), movs, adds/subs/adcs/sbcs (register form only), sub-sp, ldr/str, blx, movw,
umull, mla, subw. Measured against the ARMv7-M Architecture Reference Manual that
left the machine unable to run a PROGRAM rather than a straight line:
* NO conditional branch at all (B-cond, encoding 1101 cond imm8) -- so no loop,
no if, no early exit could ever execute.
* NO unconditional branch (B T2, 11100 imm11).
* NO CMP in either form, and only ONE flag (C). N, Z and V did not exist, so even
a hand-written branch would have had nothing to test.
* NO immediate add/sub (imm3 and imm8 forms), no logic (AND EOR ORR BIC MVN TST),
no shifts (LSL LSR ASR ROR), no MUL, no byte/halfword access, no SP-relative or
PC-relative load, no CBZ/CBNZ, no extends.
An emulator that cannot take a branch cannot run a loop, and a loop is the first
program anyone writes. So the gap was CLOSED rather than reported: this file now
carries full NZCV, the ARM add/subtract-with-carry adder that derives all four
flags from one place, the condition-code table, and the decode above.
THE OTHER DEFECT FIXED HERE, AND IT IS THE DANGEROUS ONE: the step-budget path used
to leave result at its initial 0 and halt. 0 is ALSO a legitimate exit status, so a
runaway program and a program that exited cleanly with 0 returned the SAME VALUE.
A non-completion that is indistinguishable from a real answer is worse than a crash.
Non-completion now has NAMED sentinels, matching the rv64/mips64 siblings so one
ruler can classify all of them: -1 UNSUPPORTED, -2 RAN-OFF-END, -3 FAULT.
RAN-OFF-END is structurally unreachable in this interpreter (the loop can only exit
through a halt) and that is stated rather than hidden -- the constant exists so the
classifier third state has a name here too, not because this file can produce it.
SCOPE, STATED SO NOBODY READS THIS AS COMPLETE: IT blocks (0xBF00), SVC (0xDF00),
UDF (0xDE00), LDM/STM (0xC000), REV/REVSH, and every 32-bit Thumb-2 encoding outside
{MOVW SUBW UMULL MLA LDR.W STR.W} are still UNDECODED and correctly report
UNSUPPORTED. UDF is PERMANENTLY UNDEFINED by the manual, which makes it a stable
negative control: nx_isa_cortexm_gate uses it to prove the refusal path fires.
dependencies 1 imports · 1 importers
imports: nx_syscalls_x86_64.nx
imported by: nx_isa_cortexm_gate.nx
structs
| none |
consts
| 41 | const CM_GUEST: i64 = 16777216 |
| 42 | const CM_MASK: i64 = 0xFFFFFFFF |
| 45 | const CM_WORD_BITS: i64 = 32 |
| 46 | const CM_SIGNBIT: i64 = 31 |
| 47 | const CM_NREGS: i64 = 16 |
| 48 | const CM_REG_BYTES: i64 = 128 // CM_NREGS * 8, the register file allocation |
| 49 | const CM_SP: i64 = 13 |
| 50 | const CM_LR: i64 = 14 |
| 51 | const CM_PC: i64 = 15 |
| 52 | const CM_PC_BIAS: i64 = 4 // Thumb: PC reads as the instruction address + 4 |
| 53 | const CM_HW: i64 = 2 // halfword: the Thumb instruction quantum |
| 54 | const CM_W32: i64 = 4 |
| 55 | const CM_B8: i64 = 1 |
| 56 | const CM_DEFAULT_SP: i64 = 8388608 // 0x00800000, used only by the run() convenience wrapper |
| 57 | const CM_STEP_BUDGET: i64 = 5000000 |
| 58 | const CM_EXIT_MASK: i64 = 0xff |
| 61 | const CM_UNSUPPORTED: i64 = -1 |
| 62 | const CM_RANOFF: i64 = -2 |
| 63 | const CM_FAULT: i64 = -3 |
| 66 | const CM_FLAG_N: i64 = 1 |
| 67 | const CM_FLAG_Z: i64 = 2 |
| 68 | const CM_FLAG_C: i64 = 4 |
| 69 | const CM_FLAG_V: i64 = 8 |
| 70 | const CM_FLAG_CV: i64 = 12 // CM_FLAG_C + CM_FLAG_V, the pair a logic op preserves |
| 71 | const CM_FLAGMASK: i64 = 15 |
| 72 | const CM_FLAGSHIFT: i64 = 32 // a 32-bit result never reaches bit 32, so the pack is lossless |
| 75 | const CM_SH_LSL: i64 = 0 |
| 76 | const CM_SH_LSR: i64 = 1 |
| 77 | const CM_SH_ASR: i64 = 2 |
| 78 | const CM_SH_ROR: i64 = 3 |
| 79 | const CM_ROT_MASK: i64 = 31 |
| 82 | const CM_C_EQ: i64 = 0 |
| 83 | const CM_C_NE: i64 = 1 |
| 84 | const CM_C_CS: i64 = 2 |
| 85 | const CM_C_CC: i64 = 3 |
| 86 | const CM_C_MI: i64 = 4 |
| 87 | const CM_C_PL: i64 = 5 |
| 88 | const CM_C_VS: i64 = 6 |
| 89 | const CM_C_VC: i64 = 7 |
| 90 | const CM_C_HI: i64 = 8 |
| 91 | const CM_C_LS: i64 = 9 |
| 92 | const CM_C_GE: i64 = 10 |
| 93 | const CM_C_LT: i64 = 11 |
| 94 | const CM_C_GT: i64 = 12 |
| 95 | const CM_C_LE: i64 = 13 |
| 96 | const CM_C_MAX: i64 = 13 // 14 is UDF and 15 is SVC in the B-cond encoding |
| 99 | const CM_M32_LO: i64 = 0xE800 |
| 100 | const CM_MSK_F000: i64 = 0xF000 |
| 101 | const CM_MSK_F800: i64 = 0xF800 |
| 102 | const CM_MSK_FD00: i64 = 0xFD00 |
| 103 | const CM_MSK_FE00: i64 = 0xFE00 |
| 104 | const CM_MSK_FF00: i64 = 0xFF00 |
| 105 | const CM_MSK_FF80: i64 = 0xFF80 |
| 106 | const CM_MSK_FF87: i64 = 0xFF87 |
| 107 | const CM_MSK_FFC0: i64 = 0xFFC0 |
| 108 | const CM_MSK_FBF0: i64 = 0xFBF0 |
| 109 | const CM_MSK_FFF0: i64 = 0xFFF0 |
| 112 | const CM_OP_LSLI: i64 = 0x0000 |
| 113 | const CM_OP_LSRI: i64 = 0x0800 |
| 114 | const CM_OP_ASRI: i64 = 0x1000 |
| 115 | const CM_OP_ADDR: i64 = 0x1800 |
| 116 | const CM_OP_SUBR: i64 = 0x1A00 |
| 117 | const CM_OP_ADDI3: i64 = 0x1C00 |
| 118 | const CM_OP_SUBI3: i64 = 0x1E00 |
| 119 | const CM_OP_MOVI8: i64 = 0x2000 |
| 120 | const CM_OP_CMPI8: i64 = 0x2800 |
| 121 | const CM_OP_ADDI8: i64 = 0x3000 |
| 122 | const CM_OP_SUBI8: i64 = 0x3800 |
| 123 | const CM_OP_AND: i64 = 0x4000 |
| 124 | const CM_OP_EOR: i64 = 0x4040 |
| 125 | const CM_OP_LSLR: i64 = 0x4080 |
| 126 | const CM_OP_LSRR: i64 = 0x40C0 |
| 127 | const CM_OP_ASRR: i64 = 0x4100 |
| 128 | const CM_OP_ADC: i64 = 0x4140 |
| 129 | const CM_OP_SBC: i64 = 0x4180 |
| 130 | const CM_OP_ROR: i64 = 0x41C0 |
| 131 | const CM_OP_TST: i64 = 0x4200 |
| 132 | const CM_OP_RSB: i64 = 0x4240 |
| 133 | const CM_OP_CMPR: i64 = 0x4280 |
| 134 | const CM_OP_CMN: i64 = 0x42C0 |
| 135 | const CM_OP_ORR: i64 = 0x4300 |
| 136 | const CM_OP_MUL: i64 = 0x4340 |
| 137 | const CM_OP_BIC: i64 = 0x4380 |
| 138 | const CM_OP_MVN: i64 = 0x43C0 |
| 139 | const CM_OP_ADDHI: i64 = 0x4400 |
| 140 | const CM_OP_CMPHI: i64 = 0x4500 |
| 141 | const CM_OP_MOVHI: i64 = 0x4600 |
| 142 | const CM_OP_BX: i64 = 0x4700 |
| 143 | const CM_OP_BLX: i64 = 0x4780 |
| 144 | const CM_OP_LDRLIT: i64 = 0x4800 |
| 145 | const CM_OP_STRR: i64 = 0x5000 |
| 146 | const CM_OP_STRHR: i64 = 0x5200 |
| 147 | const CM_OP_STRBR: i64 = 0x5400 |
| 148 | const CM_OP_LDRSBR: i64 = 0x5600 |
| 149 | const CM_OP_LDRR: i64 = 0x5800 |
| 150 | const CM_OP_LDRHR: i64 = 0x5A00 |
| 151 | const CM_OP_LDRBR: i64 = 0x5C00 |
| 152 | const CM_OP_LDRSHR: i64 = 0x5E00 |
| 153 | const CM_OP_STRI5: i64 = 0x6000 |
| 154 | const CM_OP_LDRI5: i64 = 0x6800 |
| 155 | const CM_OP_STRBI5: i64 = 0x7000 |
| 156 | const CM_OP_LDRBI5: i64 = 0x7800 |
| 157 | const CM_OP_STRHI5: i64 = 0x8000 |
| 158 | const CM_OP_LDRHI5: i64 = 0x8800 |
| 159 | const CM_OP_STRSP: i64 = 0x9000 |
| 160 | const CM_OP_LDRSP: i64 = 0x9800 |
| 161 | const CM_OP_ADR: i64 = 0xA000 |
| 162 | const CM_OP_ADDSPR: i64 = 0xA800 |
| 163 | const CM_OP_ADDSPI: i64 = 0xB000 |
| 164 | const CM_OP_SUBSPI: i64 = 0xB080 |
| 165 | const CM_OP_CBZ: i64 = 0xB100 |
| 166 | const CM_OP_SXTH: i64 = 0xB200 |
| 167 | const CM_OP_SXTB: i64 = 0xB240 |
| 168 | const CM_OP_UXTH: i64 = 0xB280 |
| 169 | const CM_OP_UXTB: i64 = 0xB2C0 |
| 170 | const CM_OP_PUSH: i64 = 0xB400 |
| 171 | const CM_OP_CBNZ: i64 = 0xB900 |
| 172 | const CM_OP_POP: i64 = 0xBC00 |
| 173 | const CM_OP_BKPT: i64 = 0xBE00 |
| 174 | const CM_OP_BCOND: i64 = 0xD000 |
| 175 | const CM_OP_B: i64 = 0xE000 |
| 178 | const CM_OP_MOVW: i64 = 0xF240 |
| 179 | const CM_OP_SUBW: i64 = 0xF2A0 |
| 180 | const CM_OP_UMULL: i64 = 0xFBA0 |
| 181 | const CM_OP_MLA: i64 = 0xFB00 |
| 182 | const CM_OP_LDRT4: i64 = 0xF850 |
| 183 | const CM_OP_STRT4: i64 = 0xF840 |
| 186 | const CM_IMM5: i64 = 5 |
| 187 | const CM_IMM8: i64 = 8 |
| 188 | const CM_IMM16: i64 = 16 |
| 189 | const CM_IMM11: i64 = 11 |
| 190 | const CM_M_IMM3: i64 = 7 |
| 191 | const CM_M_IMM5: i64 = 31 |
| 192 | const CM_M_IMM7: i64 = 127 |
| 193 | const CM_M_IMM8: i64 = 255 |
| 194 | const CM_M_IMM11: i64 = 0x7FF |
| 195 | const CM_M_REG3: i64 = 7 |
| 196 | const CM_M_REG4: i64 = 15 |
| 197 | const CM_M_BYTE: i64 = 255 |
| 198 | const CM_M_HALF: i64 = 0xFFFF |
| 199 | const CM_ALIGN4: i64 = 3 |
| 200 | const CM_ODDCLR: i64 = 4294967294 // CM_MASK - 1: clears the Thumb bit on a branch target |
functions
| 202 | func cm_ld(mem: *u8, va: i64, width: i64) -> i64 { var v: i64 = 0; var i: i64 = 0; while i < width { v = v | ((mem[va + i] & 0xff) << (i * 8)); i = i + 1 } return v } called by 1: emu_cortexm_run_mem |
| 203 | func cm_st(mem: *u8, va: i64, width: i64, val: i64) -> i64 { var i: i64 = 0; while i < width { mem[va + i] = (val >> (i * 8)) & 0xff; i = i + 1 } return 0 } called by 1: emu_cortexm_run_mem |
| 207 | func cm_sext(v: i64, bits: i64) -> i64 called by 1: emu_cortexm_run_mem |
| 218 | func cm_addf(a0: i64, b0: i64, cin: i64) -> i64 called by 1: emu_cortexm_run_mem |
| 237 | func cm_not(b: i64) -> i64 { return CM_MASK - (b & CM_MASK) } called by 1: emu_cortexm_run_mem |
| 240 | func cm_nzf(res0: i64, oldf: i64) -> i64 called by 1: emu_cortexm_run_mem |
| 250 | func cm_shift(kind: i64, val0: i64, sh: i64, oldf: i64) -> i64 called by 1: emu_cortexm_run_mem |
| 308 | func cm_cond(cond: i64, f: i64) -> i64 called by 1: emu_cortexm_run_mem |
| 343 | func cm_rd(r: *i64, i: i64, pc: i64) -> i64 called by 1: emu_cortexm_run_mem |
| 348 | func cm_align4(v: i64) -> i64 { return v - (v & CM_ALIGN4) } called by 1: emu_cortexm_run_mem |
| 350 | func emu_cortexm_run_mem(mem: *u8, mem_size: i64, entry: i64, sp0: i64) -> i64 called by 17: emu_cortexm_runcx_imm_arithcx_subcx_imm8_add_subcx_logiccx_shift_logical+11 calls 10: cm_ldcm_stcm_rdcm_addfcm_notcm_nzf+4 |
| 847 | func emu_cortexm_run(code: *u8, code_len: i64) -> i64 calls 1: emu_cortexm_run_mem |