Assembly Language with SAS/C

SAS/C 6.58 ships an integrated assembler invoked as sc with .s/.asm input files, plus inline assembly via #asm/#endasm (or the C-style asm() keyword). Source: Vol 2 Ch.11.

Calling sc as an assembler

sc accepts .s (assembler source) and .asm files:

sc myasm.s                    # assemble to myasm.o
sc myasm.s myprog.c           # mixed: assemble + compile + link
sc myasm.s -o myasm.o         # explicit output name

The assembler understands a Motorola 68000 mnemonics dialect with SAS extensions for sections, symbols, and external references.

Assembly source structure

* comment line — leading asterisk
;
; semicolon comments are also accepted

    SECTION mycode,CODE         ; declare a code section

    XREF    _my_c_function      ; import a C function
    XDEF    _my_asm_function    ; export this label

_my_asm_function:
    ; function prologue (preserve A4 if data segment needed)
    movem.l d0-d7/a0-a6,-(a7)
    move.l  _DataBase,A4

    ; do work
    move.l  #42,D0

    ; call into C
    jsr     _my_c_function

    ; function epilogue
    movem.l (a7)+,d0-d7/a0-a6
    rts

Sections

Directive Effect
SECTION name,CODE Code section named name
SECTION name,DATA Data section named name
SECTION __MERGED,DATA Data merged across all object files (small data model)
SECTION _NOMERGE,DATA Data never merged, even in small data model
SECTION NTRYHUNK,CODE Becomes the first segment of the executable

Symbol directives

Directive Effect
XREF name Reference an external symbol
XDEF name Export this symbol
EQU or = Define a constant
DC.b, DC.w, DC.l Define constant byte/word/long data
DS.b, DS.w, DS.l Reserve byte/word/long space

Inline assembly

Three equivalent forms put assembly directly in C code:

/* Form 1: #asm / #endasm (SAS/C extension) */
__asm void fast_copy(void *dst, void *src, int n)
{
#asm
    move.l  8(A7),A0       ; dst
    move.l  12(A7),A1      ; src
    move.l  16(A7),D0      ; n
.loop:
    move.b  (A1)+,(A0)+
    dbra    D0,.loop
    rts
#endasm
}

/* Form 2: asm("...") — single instruction */
__asm int atomic_increment(volatile int *p)
{
    asm("addq.l #1,(%a0)");     /* %a0 → A0, %d0 → D0, etc. */
    return *p;
}

/* Form 3: full inline block via __asm */
__asm {
    nop
    nop
}

Register mapping in inline assembly

Mnemonic Register
%d0–%d7 Data registers D0–D7
%a0–%a6 Address registers A0–A6
%a7 / %sp Stack pointer
%pc Program counter
%sr Status register

Constraints

  • Inline assembly cannot contain assembler directives (SECTION, EQU, etc.) — use a separate .s file for those.
  • Local C variables are not directly addressable from inline assembly; pass them as arguments or use & for their address.
  • The compiler assumes inline assembly does not clobber registers you declare as inputs. Use __asm clobber lists if needed.

Calling conventions

SAS/C follows the standard AmigaOS register-based calling convention for library calls:

  • A6 = library base (set up by OpenLibrary)
  • A0, A1 = first two pointer args
  • D0, D1 = first two integer args
  • D2 = first float arg (FFP format)
  • Stack = additional args, right to left
  • Return value in D0 (or D0/D1 for 64-bit)

For internal C-to-C calls, SAS/C defaults to the stack-based convention (__stdargs). Use __regargs to force register-based:

__regargs int fast_add(int a, int b) { return a + b; }

Library functions declared via #pragma libcall use the register-based convention automatically because that's how the jump table works.

Linking assembly and C

The standard NDK pattern:

/* myprog.c */
extern void my_asm_function(int n);

int main(int argc, char **argv)
{
    my_asm_function(42);
    return 0;
}
* myasm.s
    XREF    _main
    XDEF    _my_asm_function

_my_asm_function:
    ; ...
    rts

Compile and link:

sc myprog.c
sc myasm.s
slink from c.o myprog.o myasm.o lib:sc.lib lib:amiga.lib to myprog

Common inline assembly patterns

Atomic operations (68020+)

__asm int atomic_add(volatile int *p, int v)
{
    asm("add.l  %d1,(%a0)");      /* p in a0, v in d1 */
}

Memory barriers (68030/040/060)

__asm void cpu_cache_flush(void *addr, int n)
{
    /* 68030: cpushl instruction */
    asm("cpushl %dc,(%a0)");     /* push data cache line at a0 */
}

Direct hardware access

#define CUSTOM_BASE 0xDFF000
__asm volatile void set_color(int reg, unsigned short val)
{
    /* No C variable support here; use magic addresses */
    asm("move.w %d1,0xDFF180(%d0)");  /* color reg is at offset 0x180, val in d1, reg in d0 */
}

Pitfalls

  • Don't clobber A4 without __saveds. A4 holds the data segment base for code running with base-relative addressing. Inline assembly that uses globals (via __saveds data) must preserve A4.
  • Caches. The 68020 introduced an instruction cache, the 68030 a data cache, the 040 a unified cache, the 060 a more aggressive one. Code that writes instructions to memory (e.g., the MANX overlay manager — see AmigaOS Hunk Overlays) must flush caches to be reliable. SAS/C 6.58 does not auto-flush.
  • Branch displacement. Short branches (bsr.s, bra.s) are ±127 bytes. Long branches (bsr.w, bra.w) are ±32K. Beyond that, you need a jump table or a trampoline. Inline asm() typically does not warn about this.
  • Optimization interactions. At optimize≥2, the compiler may reorder or eliminate loads/stores around inline assembly if it doesn't see the data flow. Use __asm volatile (or the equivalent in your compiler) to prevent reordering.

See Also


Sources: SAS Institute Inc., "SAS/C Development System User's Guide, Volume 2" Ch.11, Version 6.50 (1993), pp. 307–338. Raw: raw/sasc/sasc-assembly-extract.md Updated: 2026-10-02