Skip to content

Commit edec008

Browse files
authored
0.8.0 —— trap 组补上一个动作,第四台机器实现了它 (#8)
* feat(cortex-m): the first partial backend, and a preemptive scheduler on it ## Capabilities are split, because one machine cannot do everything `openarch-backend` says a backend is present. It does not say what the machine can do, and until now it did not need to: riscv64, aarch64 and x86_64 are all application-class machines with a memory management unit. M-profile is not. Its MPU describes regions by base and limit; there is no entry naming a physical page, so `arch_pte_make_leaf` — one of the two primitives this layer's viability was decided on — has nothing to construct. The interface either refuses that machine or admits a partial backend, and refusing it would exclude the class of device this layer is most useful on. So the groups are named. A backend declares what it implements, a kernel requires what it needs, and a mismatch is reported by name at RESOLUTION rather than as a wall of `undefined reference to arch_pte_*` at link time. The three existing backends declare `openarch:address-space` and `openarch:percpu-register`; `openarch-cortex-m` declares neither, and says why in each case. ⭐ This is the mechanism openarch already used for backend selection, applied one level finer — and the same mechanism mcpp uses for target-side layers and for named runners. Three uses, one idea. ## ⭐⭐ And the example found a gap in the layer `examples/preempt` interleaves two tasks that never yield, and asserts that each observed a counter it did not advance — because two tasks that merely print would also print if the switch never happened. Its first version called `arch_context_switch` from PendSV. It compiled, linked, booted, and reported that neither task had ever observed the other: nothing failed, the tasks were simply never interleaved, and only the counter assertion told the difference. Inside an exception handler the hardware has already stacked half the register file onto the interrupted task's stack and is running on MSP. Swapping the callee-saved registers of whoever called `arch_context_switch` swaps the HANDLER's state, and the exception return unstacks a frame belonging to nobody. ⚠️ openarch has a primitive for "switch to another saved context" and none for "switch the context this trap will return to". Every architecture needs the second to preempt and every one spells it differently — riscv64 edits `sepc`, aarch64 `ELR_EL1`, x86_64 the interrupt frame — which is precisely the shape of thing this layer exists to abstract. Recorded in the example's README rather than papered over; naming it as a fifth interface group is a decision to take with more than one machine in view. `examples/switch` could not have found this. It never enters a trap. Measured on qemu `mps2-an385`: both tasks observe preemption, 1235 bytes of text. * 0.8.0 — the trap group gains an action, and the fourth machine implements it ## The primitive that was missing, and what showed it `arch_trap_set_handler` lets a kernel SEE a trap; `arch_trap_enable_interrupts` lets it MASK one. Neither can change what the trap returns to — and that is the whole of preemption, which is the principal reason to use this layer on a microcontroller at all. void arch_trap_switch(arch_trap_frame* f, void* from, void* to); Called from a handler. The interrupted context is saved through `from`, the trap resumes `to`, and the call returns normally: the switch happens when the trap does. It sits beside `arch_context_switch`, takes the same storage and the same `arch_context_init`, and differs only in WHEN it takes effect. `examples/preempt` found the gap. Its first version called `arch_context_switch` from PendSV: it built, booted, and reported that neither of two tasks had ever observed the other. Nothing failed — the tasks were simply never interleaved, and only a counter neither task advanced itself told that apart from success. ## ⭐⭐ Four machines, and they do not implement it the same way | | how the trap resumes elsewhere | |---|---| | riscv64 | a cooperative switch inside the dispatcher, `mepc` saved across it | | aarch64 | the same, `ELR_EL1` and `SPSR_EL1` saved across it | | x86_64 | the same, nothing to save — the `iret` frame travels with the stack | | Cortex-M | not that at all: pend PendSV, taken by the hardware at exception exit | The first three work because the trap runs on the interrupted context's own stack. M-profile's does not — the handler is on MSP while the task is on PSP — so a cooperative swap there swaps the HANDLER's state and the exception return unstacks a frame belonging to nobody. That difference is exactly what an interface function earns its place by hiding. ⭐ And the mechanism M-profile does offer turns out to be the interface's own sentence about the two primitives: PendSV pended from thread mode is taken AT ONCE, pended from a handler it is taken WHEN THE HANDLER EXITS. So `arch_context_switch` and `arch_trap_switch` on that backend are one instruction sequence and the hardware supplies the difference. ⚠️ That forced the Cortex-M context layout to change. Two layouts — one for the cooperative path, one for the preempted one — would let a kernel that mixed a yield with a timer corrupt a context by resuming it through the other door, silently, because both are just words. One mechanism, one layout, and the example's probe resumes a trap-saved context with the cooperative call to say so. ## The first partial backend `openarch-cortex-m` declares `openarch-backend` and `openarch:preemption`, and NOT `openarch:address-space` or `openarch:percpu-register`: M-profile has a region-based MPU with no page-table entry to construct, and no TPIDR-class register. A kernel that needs either is refused by name at resolution rather than by a wall of `undefined reference to arch_pte_*` at link time. The three application-class backends declare all four. Splitting the capability is what made admitting the machine possible, and `openarch:preemption` is what made it worth doing. ## What is asserted, and where * `examples/switch` gains a preemption probe on all three application machines: a breakpoint whose handler switches, and a counter the OTHER context advanced. A synchronous trap rather than a timer, so the probe stays one piece of code. Measured: a backend whose `arch_trap_switch` does nothing prints `steps=0` and reaches every other assertion. * `examples/preempt` runs in its own CI job on `mps2-an385`, because the gate matrix runs `examples/switch`, which needs the two capabilities this machine does not have. A gate with a branch in it stops being one. * CI greps `examples/preempt/src/main.cpp` for assembly and fails if it returns. Thirty lines of hand-written PendSV are gone; if they come back, the primitive has stopped carrying its weight. ## Also * `[xlings] deps` → `[xlings.workspace]` in every example and the template. * The engine pin moves to 2026.9.4.1, which is the release carrying the Cortex-M target rows. The OLDEST version this repository needs, not the newest that exists: pinning further ahead would make the repository unbuildable between a merge here and a release there. Measured: 3/3 host tests; `examples/switch` on riscv64, aarch64 and x86_64; `examples/preempt` on thumbv7m under xim:qemu-arm@9.2.4-1. * fix(cortex-m): three ways a deferred switch loses a context Each produced the same sentence — `no task observed the other` — which is also what a backend with no `arch_trap_switch` at all produces. The message could not tell them apart; instrumenting the counters and the addresses could. 1. THE ENTRY WINDOW. The timer was armed before the first context existed, so a tick there switched away from a context that was not yet valid. Failed about one run in three. `openarch_cm_enter` now unmasks interrupts itself, as its last instruction, where no window remains. 2. THE TICK PREEMPTS THE SWITCH. PendSV is the lowest priority — which is what makes it run after every other handler, and also what lets the tick that requested a switch interrupt the switch and request another. The stub then held one context's stack pointer and another context's `from`. PendSV now masks interrupts for the whole switch; unmasking at the end is safe because a context with interrupts masked could not have been interrupted into PendSV. 3. ⚠️⚠️ TWO TICKS, ONE SWITCH. PendSV runs only once no handler is active, so two ticks can arrive before one switch is performed. A single overwritten slot then crossed the contexts — measured: `pendsv=2998` switches performed, the second task never ran, and the two contexts held stack pointers 32 bytes apart on one stack. The FIRST `from` and the LAST `to` win. The context being saved is the one that was interrupted, and only the first call in a trap window can name it; the context to resume is whatever the caller last asked for. This is in `abi.h` rather than in this backend, because the window exists on any machine whose switch is deferred to a lower-priority exception. ⚠️ The criterion is 15 CONSECUTIVE runs. Fixes 1 and 2 each raised the pass rate without reaching 1, and a single green run would have retired either of them prematurely. Measured: 15/15 `examples/preempt` on thumbv7m; `examples/switch` still green on riscv64, aarch64 and x86_64. * fix(ci): the probe keeps its ONE architecture conditional, and the pins agree ⚠️⚠️ THE GATE CAUGHT WHAT IT EXISTS FOR. Adding the preemption probe added a second `#if defined(__` — the same trap instruction, written out again — and CI counts them: the claim the gate makes is that the probe is not TWO PROGRAMS, and a second conditional is that by the letter as well as by the check. The instruction is now a function both probes call, so the file has exactly one conditional and nothing but instructions inside it. ⚠️ And the two jobs still pinned 2026.8.21.2 move to 2026.9.4.1 with the rest. `host-encoders (ubuntu-24.04)` failed on that pin with selected RuntimeBinding glibc@2.44 requires payload '…/xim-x-glibc/2.44', but it is not installed on a fresh MCPP_HOME — a bootstrap path later releases fix. A repository whose jobs pin two different engines is also measuring two different things. Measured: examples/switch on riscv64, aarch64 and x86_64, steps=1 on each. * fix(preempt): the exit status was wrong while every printed line was right ⚠️⚠️ THE EXAMPLE SUCCEEDED AND `mcpp run` EXITED 1. `SYS_EXIT` (0x18) takes its reason code in r1 DIRECTLY; the `{reason, code}` block is `SYS_EXIT_EXTENDED` (0x20), which exists because a 32-bit r1 cannot carry both a reason and a status. This board passed the block to 0x18, so it printed `both tasks observed preemption` and then reported failure. Every assertion on the OUTPUT passed. Only the exit code disagreed — and the CI step added in this branch is what read it. ⚠️ AND THE STEP HAD TO STOP PIPING INTO `tee` TO SEE IT. `$?` after a pipeline is the last command's status, so `mcpp run | tee` would have read tee's 0 and the check would have been vacuous in exactly the way the defect needed. Measured: 5/5 runs print the success line AND exit 0.
1 parent 9dd9129 commit edec008

30 files changed

Lines changed: 1584 additions & 24 deletions

File tree

.github/workflows/ci.yml

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ jobs:
4141
# carries it, and the row is now the same shape as the other two.
4242
- { arch: x86_64, triple: x86_64-none-elf, qemu: 'xim:qemu-x86' }
4343
env:
44-
MCPP_VERSION: 2026.8.21.2
44+
# ⚠️ THE OLDEST ENGINE THIS REPOSITORY NEEDS, NOT THE NEWEST THAT EXISTS.
45+
# 2026.9.4.1 is the release carrying the Cortex-M target rows, which the
46+
# fourth backend's job below builds against. Pinning something newer would
47+
# make this repository unbuildable for the window between a merge here and
48+
# a release there, and would say a dependency exists that does not.
49+
MCPP_VERSION: 2026.9.4.1
4550
XLINGS_VERSION: v2026.8.17.2
4651
XLINGS_NON_INTERACTIVE: '1'
4752
steps:
@@ -176,6 +181,12 @@ jobs:
176181
grep -q "witness=7 before=1234" run.log
177182
grep -q "switch ok" run.log
178183
grep -q "trap: back, witness=1" run.log
184+
# ⚠️ `steps=1` AND NOT MERELY THE LINE. Every other assertion here is
185+
# printed by whichever context is running; only a counter the OTHER
186+
# context advanced says the trap resumed somewhere else. Measured: a
187+
# backend whose `arch_trap_switch` does nothing prints
188+
# `preempt: back, steps=0` and reaches every line above.
189+
grep -q "preempt: back, steps=1" run.log
179190
grep -q "cpu: percpu round-trips" run.log
180191
181192
# ⚠️ THE TEMPLATE IS RENDERED BY HAND HERE, AND IT HAS TO BE.
@@ -390,6 +401,109 @@ jobs:
390401
grep -q 'export module mcpplibs.openarch;' src/openarch.cppm
391402
echo "one package, two faces, three backends"
392403
404+
# ---------------------------------------------------------------------------
405+
# The FOURTH machine, and the only one whose backend is partial.
406+
#
407+
# ⭐⭐ IT IS A SEPARATE JOB BECAUSE IT CANNOT JOIN THE MATRIX ABOVE. Every row
408+
# there runs `examples/switch`, which needs an address space and a per-CPU
409+
# register; this machine has neither and says so in its manifest. Adding a row
410+
# would have meant a conditional inside the gate — and a gate with a branch in
411+
# it stops being one.
412+
#
413+
# What it does share is the primitive the other three grew for it:
414+
# `arch_trap_switch`. Three machines exercise it with a synchronous trap in
415+
# `examples/switch`; this one exercises it with a TIMER, which is the case the
416+
# primitive exists for and the only one that shows an interrupted context
417+
# being resumed elsewhere.
418+
#
419+
# ⚠️ AND THE ASSERTION IS PREEMPTION, NOT PROGRESS. Two tasks that print would
420+
# also print if the switch never happened and one simply ran to completion.
421+
# Measured before the primitive existed: calling `arch_context_switch` from
422+
# PendSV built, booted, and reported that neither task ever observed the
423+
# other. Only a counter neither task advanced itself tells them apart.
424+
cortex-m:
425+
name: the partial backend preempts (thumbv7m)
426+
runs-on: ubuntu-24.04
427+
timeout-minutes: 40
428+
env:
429+
MCPP_VERSION: 2026.9.4.1
430+
XLINGS_VERSION: v2026.8.17.2
431+
XLINGS_NON_INTERACTIVE: '1'
432+
steps:
433+
- uses: actions/checkout@v4
434+
435+
- name: Install xlings
436+
run: |
437+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \
438+
| bash -s "$XLINGS_VERSION"
439+
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
440+
441+
- name: Install mcpp
442+
run: |
443+
# The same wait the gate job performs, and for the same reason: a
444+
# version bump's index pointer propagates asynchronously, and a single
445+
# `xlings update` can return a stale index while reporting freshness.
446+
for attempt in 1 2 3 4 5 6; do
447+
xlings update > /dev/null 2>&1 || true
448+
if xlings install "mcpp@$MCPP_VERSION" -y -g; then break; fi
449+
if [ "$attempt" = 6 ]; then
450+
echo "::error::mcpp@$MCPP_VERSION never appeared in the index"
451+
exit 1
452+
fi
453+
echo "the index has not caught up yet (attempt $attempt of 6); waiting 60s"
454+
sleep 60
455+
done
456+
mcpp --version
457+
mcpp self config --mirror GLOBAL
458+
459+
# ⚠️ BOTH HOMES. The shim on PATH dispatches against whichever home owns
460+
# it, while `mcpp run` starts the runner through mcpp's own.
461+
- name: Install the emulator
462+
run: |
463+
xlings install xim:qemu-arm -y
464+
XLINGS_HOME="$HOME/.mcpp/registry" xlings install xim:qemu-arm -y
465+
466+
- name: The scheduler preempts on mps2-an385
467+
working-directory: examples/preempt
468+
run: |
469+
set -euo pipefail
470+
# Twice, the first allowed to fail: the toolchain payload is installed
471+
# during a build, so the first build on a machine that has never
472+
# targeted this triple is the one that installs it.
473+
mcpp build --target thumbv7m-none-eabi > /dev/null 2>&1 || true
474+
# ⚠️⚠️ NOT `mcpp run | tee`, AND THE PIPELINE IS THE POINT. `$?` after a
475+
# pipeline is the LAST command's status, so `| tee` would read tee's 0
476+
# and the exit check below would be vacuous — which is how this job
477+
# found the defect it now guards: the example printed
478+
# `both tasks observed preemption` and `mcpp run` exited 1, because
479+
# the board passed `SYS_EXIT_EXTENDED`'s block to `SYS_EXIT`. Every
480+
# assertion on the OUTPUT passed.
481+
set +e
482+
mcpp run --target thumbv7m-none-eabi > run.log 2>&1
483+
rc=$?
484+
set -e
485+
cat run.log
486+
grep -q "both tasks observed preemption" run.log \
487+
|| { echo "the tasks were never interleaved"; exit 1; }
488+
[ "$rc" = "0" ] \
489+
|| { echo "the scheduler reported success and exited $rc — check the semihosting exit call"; exit 1; }
490+
491+
# ⭐ THE EXAMPLE CONTAINS NO ASSEMBLY, AND THAT IS AN ASSERTION RATHER
492+
# THAN A REMARK. Its first version hand-wrote thirty lines of PendSV,
493+
# because the layer had no primitive for "switch the context this trap
494+
# will return to". If that code comes back, the primitive has stopped
495+
# carrying its weight and this check is where it is noticed.
496+
- name: The example is scheduling policy, not machine code
497+
run: |
498+
set -euo pipefail
499+
if grep -nE '__asm__|asm volatile' examples/preempt/src/main.cpp; then
500+
echo "the scheduler has grown assembly; arch_trap_switch should have made it unnecessary"
501+
exit 1
502+
fi
503+
grep -q 'arch_trap_switch' examples/preempt/src/main.cpp \
504+
|| { echo "the example no longer exercises arch_trap_switch"; exit 1; }
505+
echo "the scheduler is forty lines of policy"
506+
393507
# ---------------------------------------------------------------------------
394508
# The half no emulator can check.
395509
#
@@ -410,7 +524,7 @@ jobs:
410524
run:
411525
shell: bash
412526
env:
413-
MCPP_VERSION: 2026.8.21.2
527+
MCPP_VERSION: 2026.9.4.1
414528
XLINGS_VERSION: v2026.8.17.2
415529
XLINGS_NON_INTERACTIVE: '1'
416530
steps:
@@ -500,7 +614,7 @@ jobs:
500614
run:
501615
shell: bash
502616
env:
503-
MCPP_VERSION: 2026.8.21.2
617+
MCPP_VERSION: 2026.9.4.1
504618
XLINGS_VERSION: v2026.8.17.2
505619
XLINGS_NON_INTERACTIVE: '1'
506620
steps:

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@
33
The architecture-mechanism layer: execution contexts, traps and address spaces,
44
as one interface over several instruction sets.
55

6-
**Status: 0.4.0.** Four interfaces — contexts, page-table entries, traps,
7-
per-CPU state and barriers — over **three** instruction sets: riscv64, aarch64
8-
and x86_64. One probe source builds and runs on all three and produces
9-
byte-identical output.
6+
**Status: 0.8.0.** Four interfaces — contexts, page-table entries, traps,
7+
per-CPU state and barriers — over **four** instruction sets: riscv64, aarch64,
8+
x86_64 and ARM Cortex-M. One probe source builds and runs on the first three and
9+
produces byte-identical output.
10+
11+
The fourth is the first **partial** backend. M-profile has no memory management
12+
unit and no per-CPU register, so `openarch-cortex-m` declares neither
13+
`openarch:address-space` nor `openarch:percpu-register` — and a kernel that
14+
needs either is refused by name at resolution rather than by a wall of
15+
`undefined reference` at link time. It does declare `openarch:preemption`, which
16+
is the capability that made admitting a partial backend worth doing: a
17+
microcontroller is exactly where a hand-written task switcher is otherwise
18+
re-invented per project.
1019

1120
## What this is, and what it is not
1221

@@ -150,6 +159,8 @@ answer as `MAIR_EL1`, arrived at for a different reason.
150159
| | Checked by |
151160
|---|---|
152161
| The switch reaches, returns and preserves; traps classify; per-CPU round-trips; four barriers are accepted | One probe source, three emulators, in CI |
162+
| A trap resumes a **different** context | The same probe, on all three; the assertion is a counter the *other* context advanced, not that both printed |
163+
| The partial backend preempts | `examples/preempt` on `mps2-an385`, in its own job: two tasks that never yield, each proving it was interrupted |
153164
| The entry encodings | A host unit test that holds **all three** encoders at once |
154165
| The two faces declare one library | A host test of `static_assert`s, on a machine with no backend at all |
155166
| The ABI's frozen layout | `tests/abi_shape.cpp`, in byte offsets rather than in `sizeof` of another member |
@@ -334,6 +345,7 @@ loop that decides whether the layer is viable.
334345

335346
| | Status |
336347
|---|---|
348+
| A 32-bit machine with an address space | Not yet. Cortex-M is 32-bit and has no page-table entry at all, so `arch_pte_make_leaf` returning `arch_u64` has never been asked what a 32-bit entry looks like. ARMv7-A would ask it — short descriptors are 32 bits, long (LPAE) ones 64 — and mcpp carries the target rows for it since 2026.9.4.2 |
337349
| Timer ticks | **Answered, not implemented.** `examples/clock-study` reads a counter on all three machines directly and `FINDING.md` records the result: all three provide a monotonic counter with one address-free instruction, and only aarch64 reports how fast it runs. So `counter()` belongs here and `frequency()` and `set_deadline()` do not — the interface is narrower than the one that would have been written first |
338350
| Page-table **walking** | Out of scope. Building an entry is mechanism; deciding where entries go is policy, and belongs to the kernel |
339351
| A second backend for one ISA | The arrangement now supports it — `backend-riscv64` names a backend rather than an architecture — and riscv will want it: this backend traps into M-mode, and a kernel under SBI traps into S-mode |

abi/include/openarch/abi.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,65 @@ arch_trap_handler_fn arch_trap_set_handler(arch_trap_handler_fn h);
141141
void arch_trap_enable_interrupts(int on);
142142
int arch_trap_interrupts_enabled(void);
143143

144+
/* ⭐⭐ THE ACTION THE TRAP GROUP WAS MISSING: ACTING ON A TRAP RATHER THAN
145+
* OBSERVING ONE.
146+
*
147+
* `arch_trap_set_handler` lets a kernel SEE a trap and
148+
* `arch_trap_enable_interrupts` lets it MASK one. Neither lets it change what
149+
* the trap returns to — and that is the whole of preemption, which is the
150+
* principal reason to use this layer on a microcontroller at all.
151+
*
152+
* Call it from inside a handler. The context that was interrupted is saved and
153+
* a handle to it is written through `from`; the trap then resumes `to` instead.
154+
* It RETURNS NORMALLY to the handler: the switch happens when the trap does,
155+
* not at the call.
156+
*
157+
* void tick(arch_trap_frame* f) {
158+
* int next = pick();
159+
* if (next != current) {
160+
* int prev = current; current = next;
161+
* arch_trap_switch(f, &ctx[prev], &ctx[next]);
162+
* }
163+
* } // ← the switch happens after this
164+
*
165+
* ⭐ THE SAME SHAPE AS `arch_context_switch`, DIFFERING ONLY IN WHEN IT TAKES
166+
* EFFECT. One is "switch now"; this one is "switch on the way out". `from` and
167+
* `to` are the same 128-byte, 16-aligned storage, laid out by the same
168+
* `arch_context_init`, so a task can be resumed by either.
169+
*
170+
* ⚠️ EVERY MACHINE NEEDS IT AND EVERY MACHINE SPELLS IT DIFFERENTLY, WHICH IS
171+
* WHY IT IS HERE. riscv64 edits `mepc`, aarch64 `ELR_EL1`, x86_64 the interrupt
172+
* frame's `RIP`/`RSP` — and M-profile none of those, because its handler runs
173+
* on a different stack from the task and the switch has to be performed by a
174+
* pended exception. Three of the four implement it as a cooperative switch
175+
* taken inside the trap; the fourth cannot, and that difference is exactly the
176+
* thing an abstraction earns its place by hiding.
177+
*
178+
* Backends that implement it declare the capability `openarch:preemption`. A
179+
* kernel that preempts requires it, and a machine that cannot is refused by
180+
* name at resolution rather than at link time.
181+
*
182+
* ⚠️ `f` IS THE FRAME THE HANDLER RECEIVED. Passing a frame from a different
183+
* trap, or a null pointer, is undefined: a backend may read the machine state
184+
* the frame describes.
185+
*
186+
* ⚠️⚠️ CALLED MORE THAN ONCE BEFORE THE TRAP RETURNS, THE FIRST `from` AND THE
187+
* LAST `to` ARE THE ONES THAT APPLY. This is not a convenience; it is the only
188+
* consistent answer, and getting it wrong cost this layer a defect that
189+
* presented as a flake.
190+
*
191+
* The context being saved is the one that was interrupted, and only the FIRST
192+
* call in a trap window can name it — by the second, the caller's idea of
193+
* "current" has already moved. The context to resume is whatever the caller
194+
* last asked for. A backend that simply overwrote both would write one task's
195+
* saved stack pointer into another task's storage, losing both.
196+
*
197+
* It is not a theoretical window. On M-profile the switch is performed by an
198+
* exception at the LOWEST priority, so it runs only once no handler is active
199+
* — and two timer ticks can arrive first. Measured: the second task never ran,
200+
* and the two contexts held stack pointers 32 bytes apart on one stack. */
201+
void arch_trap_switch(arch_trap_frame* f, void* from, void* to);
202+
144203
/* ── openarch.cpu ──────────────────────────────────────────────────────────
145204
*
146205
* `barrier` is an `arch_barrier` — the four orderings both machines can state.

abi/mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
[package]
2525
namespace = "mcpplibs"
2626
name = "openarch-abi"
27-
version = "0.6.0"
27+
version = "0.7.0"
2828
description = "openarch's C ABI: the contract between the interface and an instruction set's backend"
2929
license = "Apache-2.0"
3030
authors = ["mcpplibs"]

backends/aarch64/mcpp.toml

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
[package]
1515
namespace = "mcpplibs"
1616
name = "openarch-aarch64"
17-
version = "0.6.0"
17+
version = "0.7.0"
1818
description = "openarch's aarch64 backend: the instructions behind the ABI"
1919
license = "Apache-2.0"
2020
authors = ["mcpplibs"]
@@ -24,7 +24,39 @@ repo = "https://github.com/mcpplibs/openarch"
2424
# package's `backend` feature requires `openarch-backend` and names nobody; the
2525
# resolver binds the one provider in the graph. A consumer's own implementation
2626
# of `openarch/abi.h` declares the same line and is selected the same way.
27-
provides = ["openarch-backend"]
27+
28+
# ⭐⭐ AND WHICH GROUPS OF THAT INTERFACE THIS BACKEND IMPLEMENTS.
29+
#
30+
# `openarch-backend` says "there is a backend here". It does not say what the
31+
# machine can do, and until every backend could do everything it did not need
32+
# to: riscv64, aarch64 and x86_64 are all application-class machines with a
33+
# memory management unit.
34+
#
35+
# ⚠️ A Cortex-M is not. M-profile has a region-based MPU and no page table, so
36+
# `arch_pte_make_leaf` — one of the two primitives this layer's viability was
37+
# decided on — has nothing to construct. The interface either refuses that
38+
# machine or admits a PARTIAL backend, and refusing it would exclude the class
39+
# of device this layer is most useful on.
40+
#
41+
# So the groups are named. A backend declares what it implements; a kernel
42+
# requires what it needs; the resolver reports a mismatch by name at RESOLUTION
43+
# rather than as a wall of `undefined reference to arch_pte_*` at link time.
44+
#
45+
# ⭐ This is the mechanism openarch already used, applied one level finer. It is
46+
# also the mechanism mcpp uses for target-side layers (docs/14) and for named
47+
# runners: a capability is data, declared by a package, and the engine knows
48+
# only that capabilities exist. Three uses, one mechanism.
49+
# ⭐ `openarch:preemption` — the trap group's ACTION, not only its observation.
50+
#
51+
# `arch_trap_switch` makes the trap resume a different context. Every machine
52+
# needs it to preempt and every machine spells it differently, which is the
53+
# shape of thing this layer exists to hide; a backend that cannot provide it —
54+
# and one could exist, on a machine with no way to change what an exception
55+
# returns to — is refused by name at resolution rather than at link time.
56+
provides = ["openarch-backend",
57+
"openarch:address-space",
58+
"openarch:percpu-register",
59+
"openarch:preemption"]
2860

2961
[build]
3062
sources = ["src/**"]

0 commit comments

Comments
 (0)