Skip to content

Add register parameter support for m68k-amiga-elf GCC (gcc-regparm.patch)#299

Open
jbilander wants to merge 9 commits into
BartmanAbyss:masterfrom
jbilander:master
Open

Add register parameter support for m68k-amiga-elf GCC (gcc-regparm.patch)#299
jbilander wants to merge 9 commits into
BartmanAbyss:masterfrom
jbilander:master

Conversation

@jbilander

@jbilander jbilander commented Mar 29, 2026

Copy link
Copy Markdown

This patch adds support for explicit asm("reg") /__asm__("reg") register
parameter constraints on function declarations, enabling the AmigaOS/vbcc-style
calling convention directly in C:

void DevOpen(struct Library *dev asm("a6"),
struct IORequest *ioreq asm("a1"),
ULONG unit asm("d0"),
ULONG flags asm("d1"));

Both asm() and __asm__() syntax are accepted.

Motivation

The current toolchain requires inline asm macro stubs for all AmigaOS library
and device driver code that uses register parameters. This patch allows writing
device drivers, library stubs and ROM-callable code in natural C with the same
syntax supported by vbcc, SAS/C and Bebbo's amiga-gcc.

Changes

Five files in GCC 15.1.0 are modified (applied after gcc-barto.patch):

  • gcc/config/m68k/m68k.h — FUNCTION_ARG_REGNO_P, CUMULATIVE_ARGS struct
  • gcc/config/m68k/m68k.cc — m68k_get_parm_regno_at, m68k_function_arg,
    m68k_function_arg_advance
  • gcc/c/c-tree.h — asm_name field in struct c_parm
  • gcc/c/c-decl.cc — build_c_parm, grokparm, push_parm_decl
  • gcc/c/c-parser.cc — parse asm() after parameter declarator

Testing

Verified on Linux Mint 22 building GCC 15.1.0 from source, and confirmed
working on real AmigaOS hardware in WinUAE:

The approach is based on the same mechanism used by Bebbo's amiga-gcc
(amiga13.x/amiga15.x branches), ported and adapted for this toolchain's
GCC 15.1 base and the A5 frame pointer convention introduced by gcc-barto.patch.

Adds support for explicit asm(\"reg\") / __asm__(\"reg\") register parameter
constraints in function declarations, enabling the AmigaOS/vbcc-style
calling convention:

  void DevOpen(struct Library   *dev   asm(\"a6\"),
               struct IORequest *ioreq asm(\"a1\"),
               ULONG             unit  asm(\"d0\"),
               ULONG             flags asm(\"d1\"));

Both asm() and __asm__() syntax are accepted.

Changes across 5 files in GCC 15.1.0:
  gcc/config/m68k/m68k.h   - FUNCTION_ARG_REGNO_P, CUMULATIVE_ARGS struct
  gcc/config/m68k/m68k.cc  - m68k_get_parm_regno_at, m68k_function_arg,
                              m68k_function_arg_advance
  gcc/c/c-tree.h            - asm_name field in struct c_parm
  gcc/c/c-decl.cc           - build_c_parm, grokparm, push_parm_decl
  gcc/c/c-parser.cc         - parse asm() after parameter declarator

Apply after gcc-barto.patch:
  patch -p1 < gcc-barto.patch
  patch -p1 < gcc-regparm.patch

Tested on Linux (m68k-amiga-elf-gcc 15.1.0) and verified on real
AmigaOS hardware in WinUAE. See:
  https://github.com/jbilander/amiga-regparm-test
Generated from NDK 3.2R4 SFD files using sfdc 1.12, with:
- inline/ headers generated via sfdc --mode=macros
- proto/ headers generated via sfdc --mode=proto
- clib/ headers generated via sfdc --mode=clib
- macros.h and stubs.h from existing Bartman installation (GCC 8+ compatible)
- Raw struct headers from NDK 3.2R4 Include_H/ unchanged
- sfdc pointer return type quirk fixed in all inline headers

Tested: SimpleDevice compiles and links correctly against these headers
using m68k-amiga-elf-gcc 15.1.0 with gcc-regparm.patch applied.

See ndk32r4/README.md for usage and reproduction instructions.
Adds a GCC poison header generated from NDK 3.2R4 SFD version tags.
Including ks13_compat.h turns any accidental call to a KS 2.0+
function into a compile error:

  #include <ks13_compat.h>  // add when targeting KS 1.3

  AllocVec(100, 0);  // error: attempt to use poisoned 'AllocVec'
  AllocMem(100, 0);  // fine - exists since KS 1.2

492 functions poisoned across all NDK 3.2R4 libraries.
Generated by gen_ks13_compat.py from ==version tags in SFD files.
@jbilander

Copy link
Copy Markdown
Author

Note: This PR was opened from my fork's master branch which contains
additional work (NDK 3.2R4 headers, build documentation) that is
covered by a separate PR #300. The only change relevant to this PR
is gcc-regparm.patch. Sorry for the noise!

…correct

Field testing (par2ser.device) exposed that asm("reg") annotations on
prototypes were silently ignored at call sites, and review of the fix
found the register map had no durable, correct carrier:

- PARM_DECLs have no assembler-name field; set_user_assembler_name on
  them aliased DECL_INCOMING_RTL storage (would ICE on a
  checking-enabled GCC build)
- cgraph frees DECL_ARGUMENTS once a function is compiled, so callers
  expanded later found no register info and silently used the stack ABI
- sibcalls restored call-saved registers (d2-d7/a2-a5) after loading
  outgoing arguments, clobbering register arguments before the jump

Changes:
- C front end: asm("reg") on parameters of non-definitions (prototypes,
  typedefs, function pointers) is now a hard error
- The register map is carried as internal decl attributes: "m68k parm
  reg" on each PARM_DECL during parsing, consolidated by
  store_parm_decls into a "m68k parm regs" (parmno -> register)
  attribute on the FUNCTION_DECL
- CUMULATIVE_ARGS carries the relevant FUNCTION_DECL via
  INIT_CUMULATIVE_ARGS / new INIT_CUMULATIVE_INCOMING_ARGS; the backend
  reads the attribute for both incoming and outgoing directions
- m68k_ok_for_sibcall_p refuses callees with register parameters

Result: definitions receive arguments in the declared registers, same-TU
C callers pass them correctly (jsr with registers loaded, no stack
pushes), and annotated prototypes fail with a clear diagnostic instead
of corrupting memory at run time.
@jbilander

Copy link
Copy Markdown
Author

Update: field testing in a real device driver (par2ser.device) uncovered
that asm("reg") annotations on prototypes were silently ignored at call
sites — the caller used the stack ABI while the callee read registers,
corrupting memory. Fixing that properly surfaced three deeper issues in
the original implementation, all resolved in the latest commit:

  1. Annotated prototypes/typedefs/function pointers are now a hard
    compile error ("register parameter 'x' is only supported on function
    definitions") instead of a silent runtime trap.
  2. The register map is now carried as internal decl attributes on the
    FUNCTION_DECL rather than via DECL_ASSEMBLER_NAME on PARM_DECLs.
    PARM_DECLs have no assembler-name field (the old approach aliased
    DECL_INCOMING_RTL and would ICE on a checking-enabled GCC), and
    cgraph frees DECL_ARGUMENTS after compiling a function, which made
    same-TU callers silently fall back to the stack ABI.
  3. Sibcalls to register-parameter functions are disabled: the sibcall
    epilogue restores call-saved registers (d2-d7/a2-a5) after the
    outgoing arguments are loaded, clobbering them; a normal jsr is
    always safe.

Supported scope: register parameters on definitions, callable from
OS/asm vectors and from C within the same translation unit; annotated
prototypes are rejected. Cross-TU prototype support (register list as a
function type attribute, as Bebbo's gcc does) remains future work.

Regression tested by rebuilding four real projects against the updated
compiler and inspecting the disassembly of each: amiga-regparm-test,
SimpleDevice, hello-world-amiga-gcc (NDK 3.2R4 proto/ headers,
correct jsr -N(a6) library calls), and par2ser.device (4 TUs + asm).
Callee-side codegen is unchanged; the same-TU caller test now emits
jsr with d2/d3/d4 loaded and no stack pushes; the negative prototype
test errors as expected. Tests added to
https://github.com/jbilander/amiga-regparm-test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant