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.sfile 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
__asmclobber lists if needed.
Calling conventions¶
SAS/C follows the standard AmigaOS register-based calling convention for library calls:
A6= library base (set up byOpenLibrary)A0,A1= first two pointer argsD0,D1= first two integer argsD2= first float arg (FFP format)- Stack = additional args, right to left
- Return value in
D0(orD0/D1for 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__savedsdata) 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. Inlineasm()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¶
- SAS/C Compiler Reference — for the
__saveds,__interrupt,__regargskeywords - SAS/C Extensions to C — for
__savedsand calling convention details - SAS/C Optimization — for cache considerations
- 68000 Programming Model
- AmigaOS Hunk Overlays — for the overlay format and MANX cache gotcha
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