agentsclimarketplace

Writing assembly

Skill gerph/riscos-agent-skills/skills/writing-assembly

Write small ARM assembly segments for RISC OS with the ObjAsm toolchain, especially veneers, callbacks, module entry points, and other interface-facing `s/*` code.From its SKILL.md

Install
npx -y skills add gerph/riscos-agent-skills --skill writing-assembly

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

13.1 KB, ~3.4k tokens by cl100k_base, as published. Nobody here has run it

Assembly Development for RISC OS with ObjAsm

This document outlines the procedures, standards, and pitfalls for writing small interface-facing ARM assembly fragments on RISC OS using the objasm assembler and riscos-link.

1. Source File Structure

objasm source files are held in an s/ directory.

Syntax Rules

  • Labels: Must start at the very beginning of the line (Column 1).
  • Instructions: Must be indented (at least one space or tab).
  • Comments: Start with a semicolon (;).
  • Directives: Essential for correct object generation.

Essential Directives

        AREA    |Name|, CODE, READONLY  ; Define the area name and type
        EXPORT  my_function             ; Make symbol visible to linker
        GET     hdr.swis                ; Include header files
        END                             ; Mandatory end of file

When keeping 32-bit ARM and AArch64 variants of the same entry-point source, use the matching header families rather than sharing the 32-bit ones. For example, 32-bit code commonly uses hdr.swis, hdr.printmacros, and hdr.procapcs; AArch64 code should use the corresponding 64-bit headers such as hdr.swis64, hdr.printmacros64, and hdr.procapcs64 when those macros are needed.

2. The Build Process

The standard RISC OS build toolchain uses a two-stage process: Assemble to Object (AOF), then Link to Executable.

Assembling (objasm)

  • Default: Produces an AOF object file.
  • Command: riscos-objasm -o o.mycode s.mycode
  • Architecture: For 32-bit code, use -32bit (though often handled by Makefiles).

Assembling AArch64 (riscos64-objasm)

  • Use riscos64-objasm for AArch64 sources. It accepts ObjAsm-style directives such as AREA, EXPORT, labels in column 1, ; comments, and END, but the instructions themselves must be AArch64 instructions.
  • Do not copy ARM32 instructions such as STMFD, LDMIA, MOV pc, lr, LDR pc, [...], or conditional data-processing forms like STRVS into AArch64 sources. Use AArch64 forms such as STP/LDP, BLR, RET, and conditional branches like BVS.
  • The riscos64-objasm wrapper supports a smaller option set than riscos-objasm. Old ObjAsm switches such as -Stamp and -quit are not accepted. A typical object build is:
        riscos64-objasm -o o64.mycode s.mycode64
  • For a raw binary blob, prefer direct binary output from the assembler:
        riscos64-objasm -bin -o driver64,ffd s.driver64
Using `riscos64-link -bin` on a bare assembler object may pull in the
64-bit C runtime and fail with unresolved `main`, constructor, finaliser, or
GOT symbols.

Linking (riscos-link)

  • Default: Produces an AIF (Absolute Interface Format) executable.
  • Utility/Binary Output: To create a raw binary (e.g., for loading into RMA or as a utility module), you must use the -bin flag.
  • Command: riscos-link -bin -o mybinary,ffc o.mycode
  • Note: Without -bin, the linker adds a header that may cause "Branch through zero" exceptions if you try to jump directly to the start of the file.

Running AArch64 builds

  • Build 64-bit module targets with riscos-amu BUILD64=1.
  • Run AArch64 modules with the 64-bit runner flag:
        riscos-build-run --64 rm64/ModuleName,ffa test,fd1 --command "RMLoad ModuleName" --command "run test"
Without `--64`, the runner may reject an AArch64 module as an unsupported
architecture.

3. Register Conventions (APCS)

When interfacing with C or the OS, follow the ARM Procedure Call Standard (APCS):

  • R0-R3: Argument passing and result return (R0). These are "caller-saved" (volatile).
  • R4-R11: Variable registers. These must be preserved by the called function.
  • R12: IP (Intra-Procedure-call scratch register). Often used as a workspace pointer.
  • R13 (SP): Stack pointer. Must be 8-byte aligned at public interfaces.
  • R14 (LR): Link register (return address).
  • R15 (PC): Program counter.

For AArch64 entry-point code, use the AArch64 procedure-call rules:

  • X0-X7: Argument passing and result return. These are caller-saved.
  • X8-X17: Caller-saved scratch registers. X16 and X17 may be used by veneers or linkage code.
  • X19-X28: Callee-saved variable registers. Use these where the 32-bit version would have used preserved registers such as R4-R9.
  • X29: Frame pointer.
  • X30: Link register.
  • SP: Stack pointer. Keep it 16-byte aligned at public interfaces and before calls into C.

4. Interface Stubs And Veneers

For most RISC OS assembly work in this environment, the code being written is a small boundary layer rather than a complete algorithm. Typical cases are:

  • a veneer that lets C call a routine needing registers outside R0-R9
  • a public entry point that external code will call directly
  • a callback or vector handler that must preserve a strict register contract

When writing this kind of code:

  • write down the exact entry contract first: input registers, output registers, preserved registers, V flag behaviour, and whether the caller expects R12 to be a private word, workspace, or scratch register
  • preserve every register the contract requires, not just the registers your current implementation happens to touch
  • keep the veneer minimal: load registers, call through, translate the return convention, and get out
  • if the target routine is C, ensure the entry path establishes the C environment correctly; if external code will call into C, this is usually a job for a CMHG generic-veneer

5. RISC OS Error Convention

RISC OS uses the V flag (Overflow) to indicate errors:

  • VC (V Clear): Success. R0 may contain a result.
  • VS (V Set): Failure. R0 contains a pointer to an error block (int error_number, char error_message[]).

In your assembly return logic:

        MOVVC   r0, #0          ; Return NULL on success
        ; V flag is preserved from the last instruction (e.g., a driver call)
        LDMFD   sp!, {r4-r12, pc}

For AArch64 module entry points, the common convention is to return an error pointer or zero in x0, and not to rely on setting V. Some legacy-style driver interfaces may still define a V-flag contract for their callback entry points. In those veneers, preserve the interface's contract deliberately: for example, branch on BVS after BLR if the driver callback promises to return errors with V set, and only copy result registers back to the caller's register block on the V-clear path.

Condition flags are only meaningful until the next instruction that changes them. Do not calculate whether an error pointer is zero with CMP and then use SWIVS/BVS as though the V flag still represented an earlier SWI or C call. If the contract is "zero means no error, non-zero means error pointer", branch on the value directly and then call OS_GenerateError on the non-zero path.

6. 32-bit Pitfalls

The Return Instruction

  • Avoid: MOVS pc, lr in 32-bit user/SVC mode. This is a 26-bit era instruction that restores the PSR from the PC's top bits, which is incorrect in 32-bit mode.
  • Correct: Use MOV pc, lr for simple returns or LDMFD sp!, {regs, pc} to return and pop registers simultaneously.

The Magic Word Requirement

For certain module-related drivers (like CDFSDriver registration), the code block in memory often requires a "Magic Word" immediately preceding the entry point.

  • CDFSDriver: Expects &EE50EE50 at entry - 4.

Register Blocks (_kernel_swi_regs)

  • The standard C structure _kernel_swi_regs only contains 10 words (R0-R9).
  • If you need to pass R11 or R12 to a routine, you cannot put them in that block. You must pass them as separate arguments to an assembly veneer.

7. AArch64 Pitfalls

Register blocks are still 32-bit fields

In the current 64-bit build environment, _kernel_swi_regs is still a block of ten 32-bit int fields for R0-R9 style SWI registers. AArch64 veneers that bridge this structure should load and store w0-w9, not x0-x9, unless a specific API explicitly defines 64-bit slots.

For example:

        LDR     x10, [sp]          ; x10 -> _kernel_swi_regs
        LDP     w0, w1, [x10, #0]
        LDP     w2, w3, [x10, #8]
        LDP     w4, w5, [x10, #16]
        LDP     w6, w7, [x10, #24]
        LDP     w8, w9, [x10, #32]

Apply the same rule to private C structures and workspace blocks: prove the C field widths and offsets before changing LDR/STR instructions to their 64-bit x forms. A pointer field normally needs an x load/store and 8-byte layout; a retained 32-bit size, handle, or RISC OS register field should use a w load/store. Blindly widening stores can overlap following fields if the structure was not changed to match.

Calling through a driver entry

Use BLR xN to call a function pointer. If the entry address came from a 32-bit RISC OS register or structure field, load it into a w register so it is zero-extended into the corresponding x register before calling:

        LDR     w10, [sp, #8]      ; 32-bit entry address
        BLR     x10

Stack alignment and link register

Keep the stack 16-byte aligned across public AArch64 calls. Save and restore x30 explicitly if your veneer calls onward:

        STP     x0, x1, [sp, #-48]!
        STR     x30, [sp, #32]
        ; ...
        LDR     x30, [sp, #32]
        ADD     sp, sp, #48
        RET

When an entry point allocates its own workspace and stack, round the allocated size and the eventual stack top to the alignment required by the architecture. The 32-bit code may also maintain sl as a stack-limit register; AArch64 code usually has no direct equivalent and should not copy sl setup mechanically.

Condition flags

AArch64 has conditional branches such as BVS and BVC, but not ARM32-style conditional stores like STRVS or STMVCIA. Split the success and error paths with conditional branches.

If a SWI or callback returns an error through V, test V immediately or preserve the result in a general register before any CMP, TST, arithmetic-with-flags, or macro that may alter NZCV. If the next interface uses an error pointer in x0 instead, treat that as a separate convention and test x0, not V.

Porting matching ARM and AArch64 entry points

When translating a small ARM entry-point file to AArch64, keep a side-by-side checklist rather than doing an instruction-for-instruction rewrite:

  • map preserved ARM registers such as R4-R9 to callee-saved AArch64 registers such as X19-X24
  • replace 32-bit APCS/procedure macros and print/SWI headers with their 64-bit equivalents
  • preserve the public contract comments, but update register names and mode assumptions where the 64-bit environment differs
  • re-check every workspace offset against the C definition after pointer-size changes
  • make stack and workspace alignment explicit, especially before calling C
  • check every conditional instruction that depended on ARM's per-instruction condition codes and split it into explicit AArch64 branches

8. Public Interface Checklist

Before considering an interface stub complete, check:

  • labels are in column 1 and non-label lines are indented, so ObjAsm parses the file as intended
  • the symbol is exported if another object must link against it
  • stack push/pop sets match exactly and leave the stack aligned at the public boundary
  • MOVS pc, lr is not used for a 32-bit return path
  • AArch64 sources use AArch64 instructions and returns (RET), not ARM32 stack or PC-manipulation instructions
  • AArch64 public call paths keep the stack 16-byte aligned and preserve x30 when calling onward
  • AArch64 ports use w loads/stores for 32-bit fields and x loads/stores only for proven 64-bit fields or pointers
  • V-flag checks happen before any flag-clobbering instruction, or the code tests an explicit error pointer/value instead
  • success and error exits match the caller's expected convention, especially whether R0/x0 should be preserved, cleared, or treated as an error pointer
  • any required magic word or descriptor layout is present at the exact offset the consumer expects

9. Debugging

  • Disassembly: Use riscos-dumpi <file> to see the actual instructions generated. This is vital for checking if the linker added unwanted headers or if the MOVVC/STRVS conditions are correct. Use riscos-dumpi --arm64 <file> for AArch64 objects or binaries.
  • Hex Dump: Use riscos-dump <file> to verify byte alignments and magic words.
  • Branch through zero: This exception usually means you jumped to a NULL pointer, often caused by an incorrect entry point address or a corrupted stack during a return.

What ships with it: 1 file

200 B alongside SKILL.md

agents/

Keep looking

Skills are one crate of 326,696. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.