gen-asm
Translate inline assembly blocks in Oberon source files
Name
gen-asm – translate inline assembly blocks in Oberon source files
Synopsis
gen-asm path [path …] [--recursive] [--dry-run] [-v]
Description
gen-asm finds (* asm ... end asm *) blocks (case-insensitive)
in Oberon .mod files and generates the corresponding SYSTEM.EMIT,
SYSTEM.EMITH, SYSTEM.LDREG, and SYSTEM.REG calls. Generated
code is placed between (* +asm *) and (* -asm *) markers. The
tool replaces the generated block on each re-run; the ASM block is
the source of truth.
Both (* ASM ... END ASM *) and (* asm ... end asm *) are
recognised. Generated markers are always lowercase. If SYSTEM
is not in the module's IMPORT list and generated code requires it,
the tool adds it automatically.
Handcrafted SYSTEM.EMIT and similar calls outside the markers are never touched.
Mnemonics, register names and system register names are case-insensitive.
Arguments
- path
- One or more
.modfiles or directories. When a directory is given, all.modfiles in that directory are processed.
Options
- --recursive, -r
- Scan directories recursively for
.modfiles. - --dry-run, -n
- Show which files would be updated without writing.
- -v, --verbose
- Print per-file status.
ASM Block Syntax
Assembly is placed in a structured comment block:
(* asm
mrs r11, PSP -> stackframeBase
dsb
isb
end asm *)
Each line contains one assembly instruction. Empty lines and Oberon
comments (* ... *) within the block are ignored.
Variable Binding
The -> operator connects assembly registers to Oberon variables:
- Output (register to variable):
mrs r11, PSP -> stackframeBase- Generates
SYSTEM.EMIT(...)followed bystackframeBase := SYSTEM.REG(11). - Input (variable to register):
ExcPrioBlock -> msr BASEPRI, r3- Generates
SYSTEM.LDREG(3, ExcPrioBlock)followed bySYSTEM.EMIT(...).
Without ->, the instruction generates only the SYSTEM.EMIT or
SYSTEM.EMITH call.
Generated Code Markers
(* asm
dsb
isb
end asm *)
(* +asm *)
SYSTEM.EMIT(0F3BF8F4FH); (* dsb *)
SYSTEM.EMIT(0F3BF8F6FH); (* isb *)
(* -asm *)
Everything between (* +asm *) and (* -asm *) is owned by the
tool and replaced on each run. On the first run, the markers and
generated code are inserted after the ASM block. Both uppercase
and lowercase markers are recognised for re-run; generated markers
are always lowercase.
Supported Instructions
Barriers
| Instruction | Encoding | Size | Description |
|---|---|---|---|
dsb |
SYSTEM.EMIT |
32-bit | Data Synchronisation Barrier |
isb |
SYSTEM.EMIT |
32-bit | Instruction Synchronisation Barrier |
dmb |
SYSTEM.EMIT |
32-bit | Data Memory Barrier |
No operands.
Hint Instructions
| Instruction | Encoding | Size | Description |
|---|---|---|---|
nop |
SYSTEM.EMITH |
16-bit | No Operation |
wfi |
SYSTEM.EMITH |
16-bit | Wait For Interrupt |
wfe |
SYSTEM.EMITH |
16-bit | Wait For Event |
sev |
SYSTEM.EMITH |
16-bit | Send Event |
No operands.
Interrupt Control
| Instruction | Encoding | Size | Description |
|---|---|---|---|
cpsie i |
SYSTEM.EMITH |
16-bit | Enable interrupts (clear PRIMASK) |
cpsid i |
SYSTEM.EMITH |
16-bit | Disable interrupts (set PRIMASK) |
cpsie f |
SYSTEM.EMITH |
16-bit | Enable faults (clear FAULTMASK) |
cpsid f |
SYSTEM.EMITH |
16-bit | Disable faults (set FAULTMASK) |
Special Register Access
| Instruction | Encoding | Size | Description |
|---|---|---|---|
mrs Rd, sysreg |
SYSTEM.EMIT |
32-bit | Read system register to Rd |
msr sysreg, Rn |
SYSTEM.EMIT |
32-bit | Write Rn to system register |
Rd, Rn: r0–r15.
Supported system registers:
| Name | SYSm | Description |
|---|---|---|
APSR_nzcvq |
00H | Condition flags |
XPSR |
03H | Combined status |
IPSR |
05H | Interrupt status |
MSP |
08H | Main Stack Pointer |
PSP |
09H | Process Stack Pointer |
MSPLIM |
0AH | MSP limit |
PSPLIM |
0BH | PSP limit |
PRIMASK |
10H | Interrupt mask |
BASEPRI |
11H | Base priority |
FAULTMASK |
13H | Fault mask |
CONTROL |
14H | Thread mode control |
MSP_NS |
88H | Non-secure MSP |
PSP_NS |
89H | Non-secure PSP |
MSPLIM_NS |
8AH | Non-secure MSP limit |
PSPLIM_NS |
8BH | Non-secure PSP limit |
CONTROL_NS |
94H | Non-secure CONTROL |
SP_NS |
98H | Current NS stack pointer |
System register names are case-insensitive.
Branch Instructions
| Instruction | Encoding | Size | Description |
|---|---|---|---|
bx Rn |
SYSTEM.EMITH |
16-bit | Branch and exchange |
bxns lr |
SYSTEM.EMITH |
16-bit | Branch to Non-secure (LR only) |
blxns Rn |
SYSTEM.EMITH |
16-bit | Branch with link to Non-secure |
Stack Instructions
| Instruction | Encoding | Size | Description |
|---|---|---|---|
pop.w {LR} |
SYSTEM.EMIT |
32-bit | Pop LR (Thumb2 wide) |
add sp, #N |
SYSTEM.EMITH |
16-bit | Adjust stack pointer (N: multiple of 4, 0–508) |
Supervisor Call
| Instruction | Encoding | Size | Description |
|---|---|---|---|
svc #N |
SYSTEM.EMITH |
16-bit | Supervisor call (N: 0–255) |
Security
| Instruction | Encoding | Size | Description |
|---|---|---|---|
tt Rd, Rn |
SYSTEM.EMIT |
32-bit | Test Target (SAU query) |
GPR Move (for register sanitisation)
| Instruction | Encoding | Size | Description |
|---|---|---|---|
mov Rd, #imm8 |
SYSTEM.EMITH |
16-bit | Move immediate to R0–R7 (imm: 0–255) |
mov.w Rd, #imm |
SYSTEM.EMIT |
32-bit | Move immediate to R0–R15 |
mov uses the 16-bit Thumb encoding and is limited to R0–R7.
mov.w uses the 32-bit Thumb2 encoding and supports all registers.
For mov.w, immediate 0 and 16-bit values (0–65535) are supported.
FPU Instructions (for register sanitisation)
| Instruction | Encoding | Size | Description |
|---|---|---|---|
vmov Sn, Rn |
SYSTEM.EMIT |
32-bit | Core register to single FP register |
vmov Dd, Rm, Rn |
SYSTEM.EMIT |
32-bit | Two core registers to double FP register |
vmsr FPSCR, Rn |
SYSTEM.EMIT |
32-bit | Write core register to FPSCR |
Sn: s0–s31. Dd: d0–d15. Rm, Rn: r0–r15.
Use vmov Dd, Rm, Rn with both core registers zeroed to clear two
single-precision registers per instruction (D0 = S0:S1, D1 = S2:S3,
etc.).
Examples
Barrier sequence
(* asm
dsb
isb
end asm *)
Read system register with variable binding
(* asm
mrs r11, PSP -> stackframeBase
end asm *)
Generates:
(* +asm *)
SYSTEM.EMIT(0F3EF8B09H); (* mrs r11, PSP *)
stackframeBase := SYSTEM.REG(11); (* r11 -> stackframeBase *)
(* -asm *)
Write system register with variable binding
(* asm
ExcPrioBlock -> msr BASEPRI, r3
end asm *)
Generates:
(* +asm *)
SYSTEM.LDREG(3, ExcPrioBlock); (* ExcPrioBlock -> r3 *)
SYSTEM.EMIT(0F3838811H); (* msr BASEPRI, r3 *)
(* -asm *)
BASEPRI critical section
(* asm
mrs r7, BASEPRI -> saved
ExcPrioBlock -> msr BASEPRI, r3
end asm *)
(* critical section body *)
(* asm
saved -> msr BASEPRI, r7
end asm *)
Secure procedure epilogue
(* asm
add sp, #20
pop.w {LR}
bxns lr
end asm *)
GPR and FPU sanitisation (before BLXNS)
(* asm
mov r0, #0
mov r1, #0
mov r2, #0
mov r3, #0
mov r4, #0
mov r5, #0
mov r6, #0
mov r7, #0
mov.w r8, #0
mov.w r9, #0
mov.w r10, #0
mov.w r12, #0
msr APSR_nzcvq, r0
vmov d0, r0, r0
vmov d1, r0, r0
vmov d2, r0, r0
vmov d3, r0, r0
vmov d4, r0, r0
vmov d5, r0, r0
vmov d6, r0, r0
vmov d7, r0, r0
vmov d8, r0, r0
vmov d9, r0, r0
vmov d10, r0, r0
vmov d11, r0, r0
vmov d12, r0, r0
vmov d13, r0, r0
vmov d14, r0, r0
vmov d15, r0, r0
vmsr FPSCR, r0
end asm *)
Process a directory
python gen-asm.py config/
Recursive processing with dry run
python gen-asm.py --recursive lib/v3.1/ --dry-run
Target Compatibility
The supported instructions use Thumb and Thumb-2 encodings common to
all ARM Cortex-M processors. The tool does not check the target – it
encodes whatever the programmer writes. Most instructions (barriers,
hints, interrupt control, mrs/msr with common registers, bx,
stack ops, mov) work across the Cortex-M family, including
Cortex-M0/M0+ (RP2040) and Cortex-M33 (RP2350, STM32).
Some instructions and registers are specific to Cortex-M33 (Armv8-M):
- Security Extension instructions:
bxns,blxns,tt - Security Extension registers:
MSP_NS,PSP_NS,MSPLIM,PSPLIM,CONTROL_NS,SP_NS BASEPRIandFAULTMASK(Armv7-M and Armv8-M, not Armv6-M)MSPLIM,PSPLIM(Armv8-M only)
Using a target-specific instruction on the wrong processor will produce a valid encoding but fault at runtime.
Limitations
- This is not a general ARM assembler. Only the instruction subset listed above is supported.
- The programmer is responsible for register selection and target compatibility. The tool does not interact with the compiler's register allocator and does not check the target architecture.
- Instructions are emitted in source order. No reordering or optimisation.
See Also
- sec-epilogue – insert Secure return epilogues
- gen-secure – generate NSC veneers and NS interface modules
Last updated: 16 March 2026