Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ set(MICRO_FORGE_SOURCES
src/arch/arm/cortex_m3/cortex_m3.cpp
src/arch/arm/cortex_m3/cortex_m3_thumb16.cpp
src/arch/arm/cortex_m3/cortex_m3_thumb32.cpp
src/arch/arm/cortex_m3/cortex_m3_thumb32_dataproc.cpp
src/arch/arm/cortex_m3/cortex_m3_thumb32_loadstore.cpp
src/arch/arm/cortex_m3/cortex_m3_interrupt.cpp
src/arch/arm/cortex_m3/cortex_m3_reset.cpp
src/arch/toy/cpu.cpp
Expand Down
39 changes: 39 additions & 0 deletions document/notes/008-armcc-ac6-firmware-corpus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 008 — armcc/AC6 固件语料 E2E (T5c)

> 2026-06-21。把真实固件语料从「1 份 Keil F103.axf」(见 [007](007-cortex-m3-f103-keil-firmware.md))扩到「3 份 CubeF1 Nucleo 示例」,作为 ctest E2E 回归门禁,补 armcc codegen 多样性。

## 背景

007 证明 Keil/MDK 固件能跑、且一次就暴露 9 处 Cortex-M3 缺口。但只有 1 份 armcc 样本(用户自写 CubeMX 工程)。要验证「任意真实固件」,需要一批 armcc 编译的标准示例。

## 目标

- 用 Keil MDK headless 批量编 CubeF1 **STM32F103RB-Nucleo** 小示例(GPIO/TIM/UART)。
- 产出的 `.axf` 作 ctest E2E fixture,**一次编译、提交进仓**,之后改动指令自动回归(CI 无 Keil)。

## 关键决策

1. **走 Keil headless(`UV4 -b`),不重生成工程**。CubeF1 自带 `.uvprojx`,直接编。
2. **复制最小子树到本地 NTFS(`D:\mf\`),不在 WSL 文件系统编**(见陷阱 3)。
3. **二进制 fixture 提交**,配方进 `test/firmware/armcc/REGENERATE.md`;vendored 子模块补丁不入库。

## 陷阱(本次核心价值,全是不靠运气撞出来的)

1. **AC5→AC6 编译器墙**:CubeF1 示例工程是 **AC5(armcc)** 配置,新版 MDK 只剩 **AC6(armclang)**(AC5 已退役)。`UV4 -b` 报 `uses ARM-Compiler 'Default Compiler Version 5' which is not available`,直接 abort。
2. **`<uAC6>` 的正确位置是 `<Target>` 直接子级**(紧跟 `<ToolsetName>`),**不是** `<TargetCommonOption>` 内。手插错位置会被静默忽略——这步靠 Keil GUI 切一次 AC6 才学到正确写法。
3. **WSL 文件系统(9p)建不了 Keil 的 `.__i` 响应文件**。经 `\\wsl.localhost` 或映射盘符(`net use Z:`)都不行(同一 9p 后端)。**必须编在本地 NTFS**——把 Drivers + 选定 Examples 复制到 `D:\mf\STM32CubeF1\`,保留 `..\..\..\..\..\Drivers` 相对深度。
4. **`--C99` 是 AC5 flag,AC6 不认**(`armclang: error: unknown argument: '--C99'`)。藏在 `<MiscControls>` 里,删掉即可(C99 由 AC6 默认给)。
5. **`<v6Lang>` 陷阱**:GUI 切 AC6 时 Keil 会写入一个语言标准字段,实测强制成 C90 → `cmsis_armclang.h` 的 `inline` 报 `unknown type name`。**删 `<v6Lang>`/`<v6LangP>`/`<v6Lto>`**,走 AC6 默认 gnu11。
6. **WSL→Windows interop 偶发挂**(`exec format error`,UV4/cmd.exe/net.exe 全挂)。挂了就在 Windows 原生跑 `.bat`。`/mnt/d/mf` 从 WSL 仍可读写,故编完拷回不阻塞。

## 验证

- 3 份 `.axf` 全 `ELF32 / ARM / entry=0x80000ed`,`Stm32f103Soc::load_elf` 直接加载。
- `FirmwareArmcc.{GpioIoToggle,TimTimeBase,UartPrintf}BootsClean` 三测全 **0 fault**(2,000,000 步)。
- 全量 ctest **247/247** 绿。
- **当前模拟器对 armcc codegen 零 fault**——和 F103.axf 一致,未提前暴露新指令缺口(那些等 T1/T2 主动挖)。

## 后续

- 想扩语料:照 `REGENERATE.md` 加示例 + 在 `test_firmware_armcc.cpp` 加 `BootsClean` 测试。
- armcc 多样性的真正价值在 **T1/T2 修完指令后**——那时这些固件成了活体验收器。
64 changes: 64 additions & 0 deletions document/notes/009-thumb2-reg-offset-loadstore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 009 — Thumb-2 寄存器偏移 load/store 修复(matrix §2 #9)

> Thumb-2 全覆盖里程碑 · T1b。修 matrix §2 高危静默 bug #9(F32-8):`ldr/str.w [Rn,Rm]` 静默算错地址。

## 背景

Thumb-2 全编码覆盖矩阵([`document/ai/thumb2-coverage-matrix.md`](../ai/thumb2-coverage-matrix.md))§2 列出 11 处 🔴 高危「静默错误」——指令不算 fault,但结果悄悄算错,固件 0-fault 跑通反而掩盖了它们。T1a 已修 #1(shift Carry 不更新)。本批 T1b 修 **#9**:寄存器偏移 load/store 静默算错地址。

## 问题

`ldr.w r0,[r1,r2]`、`str.w r0,[r1,r2]`、`strb.w`、`ldrh.w` 等所有 **register-offset** 形式(编码 hw1=0xF8x1、hw2=0002 之类)被 `t32_loadstore_single` 误当成 imm8 offset+,hw2[7:0] 当成 imm8,算出 `r1+2` 而非 `r1+r2`。**不 fault**,模拟器不自知结果错。

```
ldr.w r0,[r1,r2] → 期望 addr = 0x100 + 0x40 = 0x140
→ 实际 addr = 0x100 + 0x02 = 0x102 ← 静默错
```

## 根因(objdump 裁定,非记忆)

dispatch `(hw1 & 0xFF00) == 0xF800` 覆盖 0xF8xx 全空间。函数内 `(hw1>>7)&1` 区分 imm12(bit7=1)与其余(bit7=0)。问题在 bit7=0 的 else 分支:

- 旧代码按 `op=hw2[11:8]` switch,`case 0x0` 当「`[Rn, #+imm8]`」。
- 但 `arm-none-eabi-as` 实测:正向小立即数(`ldr.w [r1,#4]`)永远折叠进 imm12(T3,F8D1),**不会**用 imm8 offset+ 编码。所以在 bit7=0 空间,`op==0` 的**唯一**含义就是 register-offset。
- 旧 `case 0x0` 既是死分支(无有效 imm8 offset+ 编码),又把 reg-offset 的 Rm/shift 字段当 imm8 解析 —— 双重错。

区分位(objdump 全形式实测裁定):

| hw1[7] | hw2[11:8]=op | 形式 |
|--------|------|------|
| 1 | — | imm12(T3):`addr = Rn + imm12` |
| 0 | 0x0 | **register-offset**(T2):`addr = Rn + (Rm << shift2)` |
| 0 | C / B / 9 / F / D | imm8 addressing modes(T4):off- / post± / pre± |
| 0 | 其他 | IllegalInstruction |

reg-offset 字段:`Rt=hw2[15:12]`、`shift=hw2[5:4]`(LSL 0–3)、`Rm=hw2[3:0]`,无 writeback。

## 修复

[`src/arch/arm/cortex_m3/cortex_m3_thumb32_loadstore.cpp`](../../src/arch/arm/cortex_m3/cortex_m3_thumb32_loadstore.cpp) `t32_loadstore_single`:

1. **base 对齐下沉**:Rn=15 的 `Align(PC+4,4)` 从「literal 专享特判」下沉为通用 base —— reg-offset 以 PC 为基(`ldr.w r0,[pc,r1]`)也正确受益。
2. **else 分支 carve out**:`op==0` 单独走 register-offset(`addr = base + (rr(rm) << shift)`);imm8 switch 删除无效的 `case 0x0`。
3. store-to-PC-relative(imm12 Rn=15 & !load)拒绝,语义不变。

## 测试

两个新单测([`test/test_cortex_m3_advanced.cpp`](../../test/test_cortex_m3_advanced.cpp)):

- `LoadStoreWideRegisterOffset`:`str.w [r1,r2]` 断言写到 **0x140**(+ regression guard 读 0x102 应无该值);`ldr.w [r1,r2,lsl#3]` 断言读 **0x300**(shift 是关键,bug 下会读 0x132)。
- `LoadStoreWideRegisterOffsetByteHalf`:`strb.w [r1,r2]`(byte→0x110)、`ldrh.w [r1,r3]`(half←0x140)。

**关键设计**:断言**具体地址**(`bus_.read(addr, Width)`),不用 roundtrip —— roundtrip 的 str/ldr 用同一(错误)地址会互相抵消、掩盖 bug。这正是 #9 长期没被现有测试抓到的原因(`LoadStoreWideWordAndHalfwordImmediateOffsets` 就是 roundtrip)。

## 验证

- 全部编码经 `arm-none-eabi-as -mcpu=cortex-m3` + `objdump -d` 核验。
- `ctest --test-dir build` 全量 **254 绿**(原 252 + 新增 2),无回归。
- `cmake --build build -j$(nproc)` 全量编译绿。

## 陷阱 / 未竟

- **LDRSB.W / LDRSH.W(0xF9xx)仍未 dispatch**(matrix §3 缺失):`(hw1&0xFF00)==0xF800` 不匹配 0xF9xx,fallthrough → IllegalInstruction。这是整个 0xF9xx 空间缺失,范围更大,**另起一批**,本批不动。
- matrix 引用的代码行号是 T0 拆分前的(`cortex_m3_thumb32.cpp:366-474`),拆分后实际落在 `cortex_m3_thumb32_loadstore.cpp`;不影响 #9 的判断与修复,行号全量更新属另一清理任务。
- §2 剩余高危:#2 ORN.W 丢 Rn、#3 RSB 标志错、#4 LSR#0/ASR#0、#10 TBH→LDRD、#11 LDREX/STREX→STRD/LDRD —— 下一批候选。
44 changes: 44 additions & 0 deletions document/notes/010-thumb2-silent-bug-sweep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 010 — Thumb-2 §2 高危静默错误清零(T1c)

> Thumb-2 全覆盖里程碑 · T1c。继 T1a(shift Carry)、T1b(reg-offset load/store,notes 009)之后,清零 matrix §2 剩余 9 处静默错误(#2–#8、#10、#11)。**§2 高危 11/11 全部修复**。

## 背景

matrix [`document/ai/thumb2-coverage-matrix.md`](../ai/thumb2-coverage-matrix.md) §2 列出 11 处 🔴「静默错误」——指令不 fault,但结果悄悄算错/写错。本批一口气修完剩余 9 处,每条配 objdump 核验 + 针对性单测。

## 修复(逐条 + 修复点)

| # | bug | 修复 |
|---|-----|------|
| 2 | ORN.W reg 丢 Rn | `t32_dataproc_reg` op=3:`~shifted` → `Rn \| ~shifted`(Rn=15 退化为 MVN)。 |
| 3 | RSB.W 标志颠倒 | RSB(op=14)被减数是 shift 操作数;reg 与 imm 两处 `update_flags` 改传 `(Sub, shift-op, Rn)`。 |
| 4 | LSR/ASR shift-by-32 | `dataproc_reg` imm3:imm2==0:LSR→0、ASR→符号扩展(原返回 rm_val 不变)。 |
| 5 | CPSID/IE f 拨错寄存器 | `0xFFF0` 掩码忽略 bit[0] → `0xFFE0` 掩码 + bit4(E/D)/bit1(i)/bit0(f) 分发,FAULTMASK 正确。 |
| 6 | BKPT 静默 NOP | `0xBExx` 落 hints 的 NOP 兜底 → `trigger_hardfault()`(vector 3)。 |
| 7 | MUL.W Ra=15 叠 raw PC | MLA/MLS 块:`rr(15)` 加进乘积 → Ra=15 视作「无累加」(acc=0)。 |
| 8 | ADR.W off-by-2 | `t32_addsub_plain_imm`:ADDW/SUBW Rn=PC 用 `rr(15)`(raw PC)→ `Align(PC+4,4)`。 |
| 10 | TBH 掩码漏 H 位 | TBB/TBH dispatch hw2 掩码 `0xF0F0` 检查了 bit4(H)→ 改 `0xFFE0`(放开 [4:0]=H+Rm)。 |
| 11 | LDREX/STREX 撞 STRD/LDRD | 新 `t32_ldrex_strex`,mask `0xFF60==0xE840`(exclusive 空间 P=0&W=0;STRD/LDRD 必有 P 或 W,故不撞);单核 sim 简化为普通 LD/ST(STREX 总成功 Rd=0)。 |

## 关键陷阱:#10 mask 首版写错,E2E 抓到

#10 第一版把 hw2 掩码写成了 `0xFF0F`(检查 hw2[3:0])。但 **TBB 的 Rm 字段就在 hw2[3:0]**。gcc hal_uart 固件的 `tbb [pc,r4]`(hw2=`0xF004`,Rm=4≠0)因此**不匹配** TBB/TBH dispatch → fall through 到新加的 #11 LDREX dispatch(`0xE8DF & 0xFF60 == 0xE840`)→ tbb 被当 LDREX(load,Rd=hw2[15:12]=15)→ `wr(15, 读到的字节)` → **PC 跳飞到 GPIOA(0x40010804)**。

`E2E.HalUartTransmit` 以 `InstructionFetchFault` 抓到(PC 跑到外设区)。反汇编 hal_uart 定位到 `tbb [pc,r4]`,才锁定是 #10 mask 而非 #11。正确 mask 是 `0xFFE0`(检查 [15:5],放开 [4:0]=H+Rm)。

**教训**:
- mask 改动必须 objdump 验证所有变体,尤其 Rm≠0 的 TBB(之前只验证了 `tbh [pc,r0]` 这种 Rm=0 的)。
- 新加的「前置 dispatch mask」(LDREX)要确认不吞掉共享 hw1 空间的其它指令——TBB/TBH/LDREXB/H 同居 `0xE8Dx`。**dispatch 顺序(TBB/TBH → LDREX → STRD/LDRD)是 load-bearing**。
- 间接测试(roundtrip / 0-fault 固件)掩盖静默错;**针对性单测(断言具体值/地址/标志)才是安全网**。这次正是 hal_uart E2E 救了场。

## 验证

- `ctest` 全量 **263/263 绿**(254 + 9 新单测,每条 bug 一个针对性断言:`test_cortex_m3_advanced.cpp`)。
- 全部编码 `arm-none-eabi-as -mcpu=cortex-m3` + `objdump -d` 核验。
- 固件 E2E(3 份 AC6 + gcc hal_uart)全绿,证明修复不破坏真实固件启动(且 hal_uart 的 tbb 反向验证了 #10)。

## 未竟(下一里程碑 T2)

- §3 缺失指令(M3 范围内):**LDRSB.W/LDRSH.W(0xF9xx 整族)**、ORN.W/MVN.W imm、ROR(shifted-reg)+RRX、SMLAL/UMLAL(长乘累加)、CLZ/RBIT/REV.W 族、SSAT/USAT(+Q 标志)、CLREX/NOP.W hint 族、MCR/MRC 策略。
- §4 作用域门禁(ARMv7E-M DSP 指令 clean-fault 验证)、§5 测试缺口 sweep。
- 行 255 SBFX/UBFX dispatch 的 `|| (hw1&0xFB70)==0xF3C0` 是 tautological 死代码(pre-existing,clangd 报但 gcc 不报,功能靠 `is_unsigned` 正确)——可顺手清,非阻塞。
42 changes: 42 additions & 0 deletions document/notes/011-thumb2-missing-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 011 — Thumb-2 §3 缺失指令补全(T2)

> Thumb-2 全覆盖里程碑 · T2。补全 matrix §3「M3 范围内缺失指令」。继 T1(§2 静默错误 11/11 清零)之后,把模拟器从「能跑现有固件」推向「覆盖 ARMv7-M base 指令集」。全部 objdump 核验 + 单测,**ctest 271/271 绿**。

## 新增指令

| 指令 | 实现 |
|------|------|
| ORN.W / MVN.W imm | `dataproc_imm` 加 case 3:`Rn \| ~imm32`(Rn=15 退化为 MVN)。逻辑标志走 update_nz。 |
| ROR / RRX(shifted-reg operand) | `dataproc_reg` shift 加 case 3:`shift_n==0` → RRX(`(C<<31)\|(Rm>>1)`,读 PSR_C);否则 ROR by n。 |
| SMLAL / UMLAL | 扩长乘块(0xFBC0/0xFBE0):`RdHi:RdLo += signed/unsigned(Rn*Rm)`,read-before-write 累加。 |
| LDRSB.W / LDRSH.W | dispatch mask `0xFF00→0xFE00`(含 0xF9xx);handler `hw1[8]` 判 sign,load 后 byte/half sign-extend。sign store → IllegalInstruction。 |
| CLZ / RBIT / REV.W / REV16.W / REVSH.W | 新 `t32_misc_reverse`:CLZ=`std::countl_zero`,RBIT=位反转,REV/REV16/REVSH 复用 16 位族逻辑。CLZ vs REV.W 用 `hw1[7:4]`(0xB vs 0x9)区分。 |
| SSAT / USAT | 新 `t32_ssat_usat`:饱和到有/无符号范围,越界写 **APSR.Q**。sat 宽度 `hw2[4:0]`(SSAT +1),shift imm5=`(hw2[14:12]<<2)\|hw2[7:6]`。 |
| CLREX / NOP.W / YIELD.W / SEV.W | barrier handler 加 op=2(CLREX);新 `hw1==0xF3AF` handler(hints 全 no-op)。 |
| MCR / MRC | 无 handler → fall through 末尾 IllegalInstruction(架构上应 NoCoproc UsageFault;CPUError 无 NoCoproc,IllegalInstruction 为合理 clean fault)。M3 无协处理器,两份固件 0 命中。 |

## 关键设计点

- **PSR_Q(bit27)**:新增(def.h),APSR 的 MRS/MSR(sysm 0x00)读写含 Q,SSAT/USAT 饱和时置位。
- **dispatch 顺序 load-bearing**(延续 T1c #10 教训):
- SSAT/USAT(`0xF3xx`)必须**早于** dataproc-imm —— 否则 SSAT `0xF301` 命中 `(hw1&0xF800)==0xF000` 被当 ADD-imm。mask `0xFFD0` 放开 hw1[5](shift type)。
- CLZ/RBIT/REV(`0xFA00` with op2≠0)在 shift_reg 之后 —— shift_reg 只收 op2==0。
- LDRSB/SH 的 `0xFE00` mask 含 0xF8xx(无符号)+ 0xF9xx(符号)。
- MCR/MRC(`0xEExx`)不匹配任何前置 dispatch,末尾兜底。
- **字段位**(objdump 权威):`mov.w r3,r1,rrx = ea4f 0331` —— **Rd 在 hw2[11:8]、imm3 在 hw2[14:12] 是独立字段**(不是 hw2[15:12])。`usat r2,#5,r1 = f381 0205`(Rd=hw2[11:8])。这类掩码位错配是 T1c #10 的根因,本次全部 objdump 确认到位,一次通过。

## 验证

- `ctest` 全量 **271/271 绿**(263 + 8 新单测,`test_cortex_m3_advanced.cpp`)。
- 全部编码 `arm-none-eabi-as -mcpu=cortex-m3` + `objdump -d` 核验。
- 固件 E2E(3 AC6 + gcc hal_uart)全绿,不破坏真实固件启动。

## 未做(策略性)

- **BLX imm(T1)**:现 IllegalInstruction,M3 无 ARM 态,正确 fault(matrix §3:「现即可,补语义说明」)。
- **ARMv7E-M DSP**(QADD/QSUB/QDADD/QDSUB、PKHBT/PKHTB、SEL、SXTAH/UXTAH/SXTB16、UMAAL、SMLAD 族、USAD8 族):`arm-none-eabi-as -mcpu=cortex-m3` 拒绝 = M3 没有,保持 fault(matrix §4,作用域外)。需验证它们 clean-fault(不误解码进现有 handler)——这是 T3(§4 门禁)。
- **T3/§4**:作用域外指令的 clean-fault 验证;**T4/§5**:测试缺口 sweep(post-index、LDRD 全模式、flag sweep)。

## 成果

matrix §3 缺失指令基本补全(M3 范围内),模拟器指令覆盖从「够跑现有固件」提升到「ARMv7-M base 指令集覆盖」。剩余 §4(作用域门禁)+ §5(测试缺口)为收尾验证类工作。
20 changes: 20 additions & 0 deletions include/arch/arm/cortex_m3/cortex_m3.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@ class CortexM3CPU : public CPU {
Expected<uint16_t> fetch16(addr_t addr);
CPUExpected<void> execute_16bit(uint16_t insn);
CPUExpected<void> execute_32bit(uint16_t hw1, uint16_t hw2);
// 32-bit Thumb-2 family handlers — split out of execute_32bit so no single
// translation unit exceeds the DIRECTIVES 700-line cap. Each returns the
// result of its (already mask-matched) block; execute_32bit dispatches.
CPUExpected<void> t32_addsub_plain_imm(uint16_t hw1, uint16_t hw2);
CPUExpected<void> t32_dataproc_imm(uint16_t hw1, uint16_t hw2);
CPUExpected<void> t32_dataproc_reg(uint16_t hw1, uint16_t hw2);
CPUExpected<void> t32_misc_reverse(uint16_t hw1, uint16_t hw2);
CPUExpected<void> t32_ssat_usat(uint16_t hw1, uint16_t hw2);
CPUExpected<void> t32_shift_reg(uint16_t hw1, uint16_t hw2);
CPUExpected<void> t32_loadstore_single(uint16_t hw1, uint16_t hw2);
CPUExpected<void> t32_ldrex_strex(uint16_t hw1, uint16_t hw2);
CPUExpected<void> t32_tbb_tbh(uint16_t hw1, uint16_t hw2);
CPUExpected<void> t32_strd_ldrd(uint16_t hw1, uint16_t hw2);
CPUExpected<void> t32_stm_ldm(uint16_t hw1, uint16_t hw2);
// Operand helpers shared across the 32-bit handlers (promoted from the
// execute_32bit-local lambdas so the split-out handlers can use them).
data_t rr(uint8_t idx);
CPUExpected<void> wr(uint8_t idx, data_t val);
CPUExpected<data_t> br(addr_t addr, Width w);
CPUExpected<void> bw(addr_t addr, data_t val, Width w);
CPU::CPUExpected<addr_t> read_pc_raw() const;
CPU::CPUExpected<void> write_reg(uint8_t index, data_t value);

Expand Down
1 change: 1 addition & 0 deletions include/arch/arm/cortex_m3/def.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ static constexpr data_t PSR_N = 1u << 31;
static constexpr data_t PSR_Z = 1u << 30;
static constexpr data_t PSR_C = 1u << 29;
static constexpr data_t PSR_V = 1u << 28;
static constexpr data_t PSR_Q = 1u << 27;
static constexpr data_t PSR_T = 1u << 24;
static constexpr uint16_t REGCNT = 16;

Expand Down
55 changes: 55 additions & 0 deletions include/arch/arm/cortex_m3/thumb_fields.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,60 @@ constexpr uint8_t decode_key(uint16_t insn) {
return (insn >> 11) & 0x1Fu;
}

/// ARMv7-M barrel shift, returning both the shifted value and the shifter
/// carry-out (the C flag source for shift instructions). Pure model of the
/// ARM shift operation — no CPU state.
///
/// type: 0=LSL, 1=LSR, 2=ASR, 3=ROR.
/// `amount` is the RESOLVED shift amount: for immediate shifts the caller
/// converts the encoded field (LSR/ASR/ROR encoded 0 → 32; LSL encoded 0 → 0).
/// `carry_in` is the current C flag, used when amount==0 (LSL/LSR/ASR leave C
/// unchanged; ROR #0 is RRX).
struct ShiftOut {
uint32_t value;
bool carry;
};

inline ShiftOut barrel_shift(uint8_t type, uint32_t value, uint8_t amount,
bool carry_in) {
if (amount == 0) {
if (type == 3) { // ROR #0 → RRX: (C:value) >> 1, C = value[0]
return {(static_cast<uint32_t>(carry_in) << 31) | (value >> 1),
(value & 1u) != 0};
}
return {value, carry_in}; // LSL/LSR/ASR #0 → unchanged, C unchanged
}
switch (type) {
case 0: { // LSL
if (amount >= 32) {
return {0u, amount == 32 ? ((value & 1u) != 0) : false};
}
return {value << amount, ((value >> (32 - amount)) & 1u) != 0};
}
case 1: { // LSR
if (amount >= 32) {
return {0u, (value & 0x80000000u) != 0};
}
return {value >> amount, ((value >> (amount - 1)) & 1u) != 0};
}
case 2: { // ASR
if (amount >= 32) {
bool sign = (value & 0x80000000u) != 0;
return {sign ? 0xFFFFFFFFu : 0u, sign};
}
return {static_cast<uint32_t>(static_cast<int32_t>(value) >> amount),
((value >> (amount - 1)) & 1u) != 0};
}
default: { // ROR (amount > 0)
uint8_t r = amount & 31u;
if (r == 0) {
return {value, (value & 0x80000000u) != 0};
}
return {(value >> r) | (value << (32 - r)),
((value >> (r - 1)) & 1u) != 0};
}
}
}

} // namespace arm::cortex_m3::thumb
} // namespace micro_forge::cpu
Loading
Loading