Skip to content

Commit 17be228

Browse files
committed
feat(target): armv7-a, the first 32-bit row with a memory management unit
Every other 32-bit row in the table is M-profile: an MPU that describes regions by base and limit, and no page-table entry at all. A-profile has a real MMU with a walker, so `armv7a-none-eabi` / `-eabihf` are the first targets on which an address-space abstraction can be asked what a 32-BIT machine's entry looks like — short descriptors are 32 bits wide, long (LPAE) ones 64. openarch's layer has never been able to put that question to a 32-bit machine. Both rows are `verified`, and the column records what was RUN. Measured 2026-09-04 under `xim:qemu-arm@9.2.4-1`: each built an image that booted on `-M virt -cpu cortex-a15`, printed over semihosting and reported its exit status. ## ⚠️ The soft row needs `-mfpu=none`, measured on THIS architecture `armv7-a` is not `thumbv7em`, so the M-profile measurement says nothing about it. Measured on llvm 22.1.8, the same float multiply: 1 VFP instruction under the soft-float ABI, 0 with `-mfpu=none`. ## ⚠️ And the semihosting exit call is spelled differently `SYS_EXIT` (0x18) on AArch32 takes the reason code in `r1` DIRECTLY; the `{reason, code}` block every Cortex-M board here passes is `SYS_EXIT_EXTENDED` (0x20), which exists because a 32-bit `r1` cannot carry both. Measured: passing the block to 0x18 prints correctly and reports the WRONG exit status — a program exiting 0 reported 1. `tests/e2e/336` asserts the status and not only the output, and takes it from qemu rather than from the tail of a pipeline. ## ⭐ The unit test's predicate was a spelling, not a property `SoftFloatMProfileRowsDisableTheFpu` filtered on `starts_with("thumb")`. The rule it states applies to every 32-bit ARM row with a float-ABI suffix, so adding these two left the rule in force and the test SILENTLY not covering them: the loop skipped the new rows and every assertion still passed. The predicate now asks the property, and the test counts A-profile rows so the denominator cannot go back to zero unnoticed. 97/97 unit; e2e 332, 336 green.
1 parent 69c1cfd commit 17be228

8 files changed

Lines changed: 313 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,27 @@ CycloneDX 1.5,覆盖**已记录**的解析。⚠️ 读锁而不是重新解析
8585
**点名它本来要装的集合**,于是 `build``run` 两条命令的差集就是被测的性质,
8686
一次下载都不需要(`tests/e2e/335`)。
8787

88+
### ⭐ ARMv7-A:第一个带内存管理单元的 32 位目标行
89+
90+
`armv7a-none-eabi``armv7a-none-eabihf` 两行,`verified`。表里其余每个 32 位行都是
91+
M-profile:MPU 按基址与上限描述区域,没有页表项。A-profile 有真正的 MMU,于是它是第一个
92+
能被问「**32 位**机器的页表项长什么样」的目标 —— 这正是 openarch 的地址空间抽象从未被
93+
一台 32 位机器问过的问题。
94+
95+
实测 2026-09-04(`xim:qemu-arm@9.2.4-1`):两行都在 `-M virt -cpu cortex-a15` 上启动、
96+
经半主机打印并报回退出状态。
97+
98+
⚠️ **软浮点行同样需要 `-mfpu=none`,而这是在这个架构上重新实测的**,不是从 M-profile
99+
推过来的:`armv7a-none-eabi` 在软浮点 ABI 下对一次 float 乘法仍发出 VFP 指令。
100+
101+
⚠️ **半主机的退出调用与 M-profile 拼法不同。** AArch32 的 `SYS_EXIT`(`0x18`)把原因码
102+
**直接**放在 `r1`;Cortex-M 传的 `{reason, code}` 块是 `SYS_EXIT_EXTENDED`(`0x20`)。
103+
实测:把块传给 `0x18` 打印正确而**退出状态是错的**,只看输出的测试看不出来。
104+
105+
⭐ 那条量化 `-mfpu=none` 的单元测试,谓词曾是 `starts_with("thumb")` —— 一个**拼法**
106+
而不是它要陈述的性质。新行加进来时规则适用而测试**静默跳过**了它们,每条断言依然通过。
107+
谓词已改为「32 位 ARM」。
108+
88109
### 发现性
89110

90111
`mcpp why runners` 列出本工程提供的 runner,与其余解析结果并列;

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,7 @@ the right toolchain payload is resolved and installed automatically.
386386
| `thumbv7m-none-eabi` | llvm 22 — Cortex-M3, bare metal ² ||
387387
| `thumbv7em-none-eabihf` | llvm 22 — Cortex-M4F/M7F, hard float ² ||
388388
| `thumbv8m.main-none-eabi` | llvm 22 — Cortex-M33/M55, soft float ² ||
389+
| `armv7a-none-eabi` · `armv7a-none-eabihf` | llvm 22 — Cortex-A 32-bit, bare metal ² ||
389390
| `thumbv7em-none-eabi` · `thumbv8m.base-none-eabi` · `thumbv8m.main-none-eabihf` | llvm 22 — builds and links; no emulator run recorded | 🔄 |
390391
| `riscv64-linux-musl` || 🔄 |
391392
| `aarch64-linux-gnu` || 🔄 |

docs/13-baremetal.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ covers the hosted link model this chapter departs from.
1313
## Overview
1414

1515
A freestanding target is a target whose `os` field is `none`. The target table
16-
at `modules/toolchain-model/src/triple.cppm` carries eleven of them:
16+
at `modules/toolchain-model/src/triple.cppm` carries thirteen of them:
1717

1818
| Triple | Tier | C library |
1919
|---|---|---|
@@ -28,10 +28,30 @@ at `modules/toolchain-model/src/triple.cppm` carries eleven of them:
2828
| `thumbv8m.base-none-eabi` | preview | none by default — Cortex-M23 |
2929
| `thumbv8m.main-none-eabi` | verified | none by default — Cortex-M33/M55, soft float |
3030
| `thumbv8m.main-none-eabihf` | preview | none by default — Cortex-M33F/M55F, hard float |
31+
| `armv7a-none-eabi` | verified | none by default — Cortex-A 32-bit, soft float |
32+
| `armv7a-none-eabihf` | verified | none by default — Cortex-A 32-bit, hard float |
3133

3234
`verified` means an image has been built **and run** for the row. `preview`
3335
means it builds and links, and no emulator run has been recorded.
3436

37+
### ARMv7-A is the first 32-bit row with a memory management unit
38+
39+
Every other 32-bit row above is M-profile: an MPU that describes regions by base
40+
and limit, and no page-table entry at all. A-profile has a real MMU with a
41+
walker, so it is the first target on which an address-space abstraction can be
42+
asked what a *32-bit* machine's entry looks like — short descriptors are 32 bits
43+
wide, long (LPAE) ones 64. That question cannot be put to a machine with no
44+
entries, which is why `openarch`'s Cortex-M backend declines the capability.
45+
46+
**The semihosting exit call is not spelled the way M-profile spells it.**
47+
`SYS_EXIT` (`0x18`) on AArch32 takes the reason code in `r1` *directly*; the
48+
`{reason, code}` block a Cortex-M board passes is `SYS_EXIT_EXTENDED` (`0x20`),
49+
which exists because a 32-bit `r1` cannot carry both. Measured: passing the
50+
block to `0x18` prints correctly and then reports the wrong exit status, so a
51+
board that only checks its output cannot see the difference. This is a *board*
52+
fact rather than a target fact; it is recorded here because it is where the next
53+
person writing such a board will look.
54+
3555
### M-profile is seven rows rather than one
3656

3757
Every other bare-metal family above is one row per architecture. Cortex-M is

docs/zh/13-baremetal.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
## 概述
1212

1313
freestanding 目标是 `os` 字段为 `none` 的目标。`modules/toolchain-model/src/triple.cppm`
14-
的目标表中有十一个:
14+
的目标表中有十三个:
1515

1616
| Triple | 档位 | C 库 |
1717
|---|---|---|
@@ -26,10 +26,25 @@ freestanding 目标是 `os` 字段为 `none` 的目标。`modules/toolchain-mode
2626
| `thumbv8m.base-none-eabi` | preview | 默认无 —— Cortex-M23 |
2727
| `thumbv8m.main-none-eabi` | verified | 默认无 —— Cortex-M33/M55,软浮点 |
2828
| `thumbv8m.main-none-eabihf` | preview | 默认无 —— Cortex-M33F/M55F,硬浮点 |
29+
| `armv7a-none-eabi` | verified | 默认无 —— Cortex-A 32 位,软浮点 |
30+
| `armv7a-none-eabihf` | verified | 默认无 —— Cortex-A 32 位,硬浮点 |
2931

3032
`verified` 意味着该行的镜像被构建**并被运行**过。`preview` 意味着它构建并链接得出,
3133
尚无模拟器运行记录。
3234

35+
### ARMv7-A 是第一个带内存管理单元的 32 位行
36+
37+
上表其余每一个 32 位行都是 M-profile:MPU 按基址与上限描述区域,根本没有页表项。
38+
A-profile 有真正的 MMU 与页表走查器,所以它是第一个能被问「**32 位**机器的页表项长
39+
什么样」的目标 —— 短描述符 32 位宽,长描述符(LPAE)64 位。这个问题无法向一台没有
40+
页表项的机器提出,这正是 `openarch` 的 Cortex-M 后端不声明该能力的原因。
41+
42+
**半主机的退出调用与 M-profile 的拼法不同。** AArch32 的 `SYS_EXIT`(`0x18`)把
43+
原因码**直接**放在 `r1` 里;Cortex-M 板级代码传的那个 `{reason, code}` 块是
44+
`SYS_EXIT_EXTENDED`(`0x20`),它存在的理由正是 32 位的 `r1` 装不下两者。实测:把
45+
块传给 `0x18`,打印正确而**退出状态是错的** —— 只看输出的板子看不出这个差别。这是
46+
****的事实而不是目标的事实,记在这里是因为下一个写这种板的人会来这里找。
47+
3348
### M-profile 是七行而不是一行
3449

3550
上面每一个裸机族都是一个架构一行。Cortex-M 不是:为 `thumbv7em` 构建的目标文件

modules/toolchain-model/src/triple.cppm

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,24 @@ inline constexpr TargetInfo kKnownTargets[] = {
394394
{ "thumbv8m.base-none-eabi","preview", "bare","llvm@22.1.8","", true },
395395
{ "thumbv8m.main-none-eabi","verified", "bare","llvm@22.1.8","", true },
396396
{ "thumbv8m.main-none-eabihf","preview","bare","llvm@22.1.8","", true },
397+
// ── ARMv7-A (Cortex-A, 32-bit) ──────────────────────────────────────────
398+
//
399+
// ⭐ NOT A SECOND SPELLING OF THE M ROWS. A-profile has a memory management
400+
// unit and a page-table walker; M-profile has an MPU and no page-table
401+
// entry at all. It is the first 32-bit machine in this table on which an
402+
// address space can be described, which is precisely the question the
403+
// openarch layer has never been able to ask of a 32-bit target.
404+
//
405+
// ⚠️ `verified` records what was RUN. Measured 2026-09-04 under
406+
// `xim:qemu-arm@9.2.4-1`: both rows booted on `-M virt -cpu cortex-a15` and
407+
// printed over semihosting. The soft row carries `-mfpu=none` for the
408+
// reason `kThumbSoftExtra` gives, measured again on this architecture
409+
// rather than carried over from M-profile.
410+
//
411+
// `sysroot` is empty, the zero-libc tier, exactly as for the M rows: a C
412+
// library for these targets arrives from the dependency graph.
413+
{ "armv7a-none-eabi", "verified", "bare","llvm@22.1.8","", true },
414+
{ "armv7a-none-eabihf", "verified", "bare","llvm@22.1.8","", true },
397415
};
398416

399417
inline std::span<const TargetInfo> known_targets() { return kKnownTargets; }

src/freestanding/target.cppm

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,22 @@ inline constexpr std::string_view kThumbSoftExtra[] = {
148148
"-mfpu=none",
149149
};
150150

151+
// ⭐⭐ AND THE SAME RULE ON A-PROFILE, WHERE IT WAS MEASURED SEPARATELY RATHER
152+
// THAN ASSUMED TO CARRY OVER.
153+
//
154+
// `armv7a-none-eabi` is a different architecture from `thumbv7em`, so the
155+
// M-profile measurement above says nothing about it. Measured on llvm 22.1.8,
156+
// the same float multiply:
157+
//
158+
// clang --target=armv7a-none-eabi -march=armv7-a -O2 -S → 1 vmul/vmla
159+
// …the same, with -mfpu=none → 0
160+
//
161+
// A Cortex-A without a VFP is unusual and permitted, and a Cortex-R more so.
162+
// The row states the property its name claims, exactly as the M rows do.
163+
inline constexpr std::string_view kArmSoftExtra[] = {
164+
"-mfpu=none",
165+
};
166+
151167
// ⚠️ THE `libdir` COLUMN WAS EMPTY ON THE LAST TWO ROWS UNTIL 2026-08-21, AND
152168
// THAT WAS CORRECT UNTIL THE DAY IT WAS NOT.
153169
//
@@ -249,6 +265,38 @@ inline constexpr Spec kTable[] = {
249265
{ "thumbv8m.base-none-eabi", "armv8-m.base","aapcs","", "", kThumbSoftExtra },
250266
{ "thumbv8m.main-none-eabi", "armv8-m.main","aapcs","", "", kThumbSoftExtra },
251267
{ "thumbv8m.main-none-eabihf","armv8-m.main","aapcs","", "" },
268+
// ── ARMv7-A (Cortex-A, 32-bit) ──────────────────────────────────────────
269+
//
270+
// ⭐ THE FIRST 32-BIT MACHINE WITH A MEMORY MANAGEMENT UNIT, AND THAT IS
271+
// WHY IT IS HERE RATHER THAN BEING A SECOND SPELLING OF THE M ROWS.
272+
//
273+
// Every other 32-bit row in this table is M-profile: an MPU that describes
274+
// regions by base and limit, and no page-table entry at all. A-profile has
275+
// a real MMU with a page-table walker, so it is the first target on which
276+
// an address-space abstraction has to answer what a 32-bit machine's entry
277+
// looks like — short descriptors are 32 bits wide, long (LPAE) ones 64.
278+
// That question cannot be asked on a machine with no entries.
279+
//
280+
// ⚠️ `mcmodel` and `libdir` are empty for the reasons the M rows give.
281+
// `lldEmulation` is empty because clang's BareMetal toolchain covers arm:
282+
// measured, the driver reaches `ld.lld` and the x86_64 row's problem does
283+
// not recur.
284+
//
285+
// ⚠️ THE TIER IS `verified` AND THE MEASUREMENT IS NAMED. 2026-09-04 under
286+
// `xim:qemu-arm@9.2.4-1`: both rows built an image that BOOTED on
287+
// `-M virt -cpu cortex-a15`, printed over semihosting and reported its exit
288+
// status.
289+
//
290+
// ⚠️ AND THE SEMIHOSTING EXIT CALL IS NOT SPELLED THE WAY M-PROFILE SPELLS
291+
// IT. `SYS_EXIT` (0x18) on AArch32 takes the reason code in `r1` directly;
292+
// the `{reason, code}` block every Cortex-M board here passes is
293+
// `SYS_EXIT_EXTENDED` (0x20), which exists because a 32-bit `r1` cannot
294+
// carry both. Passing the block to 0x18 prints correctly and then reports
295+
// the WRONG exit status — measured: a program exiting 0 reported 1. That is
296+
// a board fact rather than a target fact, recorded here because it is where
297+
// the next person to write such a board will look.
298+
{ "armv7a-none-eabi", "armv7-a", "aapcs", "", "", kArmSoftExtra },
299+
{ "armv7a-none-eabihf", "armv7-a", "aapcs", "", "" },
252300
};
253301

254302
// The single read point. Returns nullopt for anything that is not a known
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#!/usr/bin/env bash
2+
# requires: llvm unix-shell qemu-arm
3+
# ARMv7-A: the first 32-bit row in the table with a memory management unit.
4+
#
5+
# ⚠️ THE ROW IS NOT A SECOND SPELLING OF THE M ROWS, AND THIS TEST IS WHERE THAT
6+
# BECOMES CHECKABLE. Every other 32-bit target here is M-profile — an MPU that
7+
# describes regions by base and limit, with no page-table entry at all — so it
8+
# is the one machine class on which an address-space abstraction has never been
9+
# asked what a 32-bit entry looks like.
10+
#
11+
# ⚠️ AND THE SEMIHOSTING EXIT CALL IS SPELLED DIFFERENTLY FROM M-PROFILE.
12+
# `SYS_EXIT` (0x18) on AArch32 takes the reason code in `r1` DIRECTLY; the
13+
# `{reason, code}` block a Cortex-M board passes is `SYS_EXIT_EXTENDED` (0x20).
14+
# Measured: passing the block to 0x18 prints correctly and then reports the
15+
# wrong exit status. The assertion below reads the STATUS, not only the output,
16+
# which is what makes that difference visible here rather than on a board.
17+
set -e
18+
19+
MCPP="${MCPP:-mcpp}"
20+
work="$(mktemp -d)"
21+
trap 'rm -rf "$work"' EXIT
22+
23+
qemu_arm() {
24+
local d c
25+
for d in "${MCPP_HOME:-$HOME/.mcpp}/registry" "$HOME/.xlings"; do
26+
c=$(ls "$d"/data/xpkgs/xim-x-qemu-arm/*/bin/qemu-system-arm 2>/dev/null | sort -V | tail -1)
27+
[ -n "$c" ] && [ -x "$c" ] && { echo "$c"; return 0; }
28+
done
29+
command -v qemu-system-arm 2>/dev/null && return 0
30+
return 1
31+
}
32+
QEMU="$(qemu_arm)" || { echo "SKIP: qemu-system-arm not installed"; exit 0; }
33+
34+
llvm_tool() {
35+
local c
36+
c=$(ls "${MCPP_HOME:-$HOME/.mcpp}"/registry/data/xpkgs/xim-x-llvm/*/bin/"$1" 2>/dev/null | sort -V | tail -1)
37+
[ -n "$c" ] && { echo "$c"; return 0; }
38+
command -v "$1" 2>/dev/null
39+
}
40+
NM="$(llvm_tool llvm-nm)" || { echo "SKIP: llvm-nm not found"; exit 0; }
41+
OBJDUMP="$(llvm_tool llvm-objdump)" || { echo "SKIP: llvm-objdump not found"; exit 0; }
42+
43+
mkdir -p "$work/soc/src"
44+
cd "$work/soc"
45+
printf '[package]\nname = "soc"\nversion = "0.1.0"\n' > mcpp.toml
46+
47+
cat > src/start.S <<'ASM'
48+
.section .text.start,"ax"
49+
.global _start
50+
_start:
51+
ldr sp, =__stack_top
52+
bl kmain
53+
1: b 1b
54+
ASM
55+
56+
cat > src/main.cpp <<'CPP'
57+
namespace {
58+
inline void sh(int op, const void* a) {
59+
register int r0 __asm__("r0") = op;
60+
register const void* r1 __asm__("r1") = a;
61+
__asm__ volatile("svc 0x123456" :: "r"(r0), "r"(r1) : "memory");
62+
}
63+
}
64+
// Referenced by nothing. --gc-sections must remove it.
65+
extern "C" void collected_because_nothing_calls_it() { sh(0x04, (void*)"UNREACHABLE\n"); }
66+
67+
extern "C" void kmain() {
68+
sh(0x04, (void*)"armv7a ok\n");
69+
// ADP_Stopped_ApplicationExit, in r1 itself — see the header note.
70+
sh(0x18, (void*)0x20026);
71+
for (;;) {}
72+
}
73+
CPP
74+
75+
cat > build.mcpp <<'BUILD'
76+
import mcpp;
77+
int main() { mcpp::link_script("link.ld"); return 0; }
78+
BUILD
79+
80+
# qemu `-M virt` puts RAM at 0x40000000. A board fact, written here.
81+
cat > link.ld <<'LD'
82+
ENTRY(_start)
83+
MEMORY { RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 16M }
84+
SECTIONS {
85+
.text : { KEEP(*(.text.start)) *(.text*) *(.rodata*) } > RAM
86+
.data : { *(.data*) } > RAM
87+
.bss : { *(.bss*) *(COMMON) } > RAM
88+
. = ALIGN(16);
89+
__stack_top = ORIGIN(RAM) + LENGTH(RAM);
90+
}
91+
LD
92+
93+
ran=0
94+
boot_row() {
95+
local triple=$1
96+
rm -rf target
97+
"$MCPP" build --target "$triple" >/dev/null 2>&1 || {
98+
echo "FAIL: $triple did not build"; exit 1; }
99+
local elf
100+
elf=$(find target -type f -name soc | head -1)
101+
[ -n "$elf" ] || { echo "FAIL: $triple produced no artefact"; exit 1; }
102+
if [ "$("$NM" "$elf" | grep -c collected_because_nothing_calls_it)" != "0" ]; then
103+
echo "FAIL: $triple kept a function nothing calls (--gc-sections not applied)"; exit 1
104+
fi
105+
local out rc
106+
# ⚠️ NOT `qemu | head`, AND THAT IS THE WHOLE POINT OF THE STATUS CHECK.
107+
# `$?` after a pipeline is the LAST command's status, so piping into `head`
108+
# would read head's 0 and the exit assertion below would be vacuous — this
109+
# repository has shipped that shape before. The output goes to a file and
110+
# the status is taken from qemu itself.
111+
set +e
112+
timeout 30 "$QEMU" -M virt -cpu cortex-a15 -nographic -semihosting \
113+
-no-reboot -kernel "$elf" > qemu.log 2>&1
114+
rc=$?
115+
set -e
116+
out=$(head -3 qemu.log)
117+
case "$out" in
118+
*"armv7a ok"*) ;;
119+
*) echo "FAIL: $triple did not boot; got: $out"; exit 1 ;;
120+
esac
121+
# ⭐ THE EXIT STATUS, NOT ONLY THE OUTPUT. A program that prints and then
122+
# gets its exit call wrong is exactly what this row's semihosting note is
123+
# about, and only the status tells the two apart.
124+
[ "$rc" = "0" ] || { echo "FAIL: $triple booted but exited $rc"; exit 1; }
125+
echo " ok $triple booted on virt/cortex-a15 and exited 0"
126+
ran=$((ran + 1))
127+
}
128+
129+
boot_row armv7a-none-eabi
130+
boot_row armv7a-none-eabihf
131+
[ "$ran" = "2" ] || { echo "FAIL: expected 2 rows to boot, got $ran"; exit 1; }
132+
133+
# ── The float ABI, both sides, on this architecture ────────────────────────
134+
#
135+
# ⚠️ MEASURED HERE RATHER THAN CARRIED OVER FROM M-PROFILE. `armv7-a` is a
136+
# different architecture from `thumbv7em`; that the soft ABI still reaches the
137+
# FPU there says nothing about here. It does — measured — and the row carries
138+
# `-mfpu=none` for it.
139+
mkdir -p "$work/fp/src"
140+
cd "$work/fp"
141+
cp "$work/soc/build.mcpp" "$work/soc/link.ld" .
142+
cp "$work/soc/src/start.S" src/
143+
printf '[package]\nname = "fp"\nversion = "0.1.0"\n' > mcpp.toml
144+
cat > src/main.cpp <<'CPP'
145+
volatile float fa = 3.0f, fb = 4.0f, fout = 0.0f;
146+
extern "C" void kmain() { fout = fa * fb + 1.0f; for (;;) {} }
147+
CPP
148+
149+
rm -rf target
150+
"$MCPP" build --target armv7a-none-eabihf >/dev/null 2>&1 || {
151+
echo "FAIL: the hard-float row did not build"; exit 1; }
152+
hard=$("$OBJDUMP" -d "$(find target -type f -name fp | head -1)" \
153+
| grep -cE '\bv[a-z]+\.f32' || true)
154+
[ "$hard" -gt 0 ] || {
155+
echo "FAIL: armv7a-none-eabihf emitted no FPU instruction — the control is vacuous"; exit 1; }
156+
echo " ok armv7a-none-eabihf uses the FPU ($hard instructions)"
157+
158+
# The soft row's proof is its link failure against `__aeabi_fmul`: with
159+
# `-mfpu=none` the multiply lowers onto a libcall, and this tier has no C
160+
# library and no builtins for it to resolve against.
161+
rm -rf target
162+
soft_out=$("$MCPP" build --target armv7a-none-eabi 2>&1 || true)
163+
case "$soft_out" in
164+
*__aeabi_fmul*) echo " ok armv7a-none-eabi lowered the multiply onto a libcall, not the FPU" ;;
165+
*) echo "FAIL: armv7a-none-eabi did not reference __aeabi_fmul; -mfpu=none may not be applied"
166+
echo "$soft_out" | tail -5 | sed 's/^/ /'; exit 1 ;;
167+
esac
168+
169+
echo "PASS: armv7-a rows build, boot, exit cleanly and honour the float ABI"

0 commit comments

Comments
 (0)