There are two options of using assembler with GCC:
  • use of inline assembly
  • use of separate .s files (compile with m68k-atari-mint-gcc as usual)
In either case, your assembly code will be interpreted by GAS (GNU assembler). It has the reputation of not being a nice assembler (designed primarily to assemble GCC output), however it has been improved and supports Motorola syntax. You cannot write advanced macros, however anything else will work.

Inline assembly is nice, but the syntax for specifying inputs, outputs and clobbered registers is difficult.

An example of separate m68k assembler function:
example content of external m68k GAS assembly file
.globl _plus | This symbol will be externally visible _plus: move.l 4(sp),d0 add.l 8(sp),d0 rts
note: Comments begin with a pipe "|".

Registers may be prefixed by % like %d0 but in our binutils configuration this is not mandatory. Beware at the symbols exported with ".globl" directive. They must begin with an underscore to be accessible from C code (they should be without "_"(one underscore)).

Put the following prototype in your C header:
example content of C header with prototype of m68k assembly function
#ifndef __PLUS_H__ #define __PLUS_H__ ;we want to include header once #ifdef __cplusplus extern "C" { #endif long plus(long a, long b); #ifdef __cplusplus } #endif #endif



The #ifdefs (__cplusplus) are necessary only if you plan to call the function from C++ code.

Below there's a standalone "Hello, World !" program. It has similar syntax to HiSoft Devpac one.

hello.s source
* A simple TOS program pea msg move.w #9,-(sp) trap #1 | Cconws() addq.w #6,sp move.w #8,-(sp) trap #1 | Cnecin() addq.w #2,sp clr.w -(sp) trap #1 | Pterm0() msg: .ascii "Hello !\r\n\0"



The following directives will be quite useful too. They have their counterparts in HiSoft Devpac:
example m68k GAS directives
.text .data .bss .rept .endr .dc.b .dc.w .dc.l .ds.b .ds.w .ds.l


GAS supports a special jump instructions named "jbsr" and "jbra". They are replaced by the smallest possible jump instruction. For example, GCC always call the functions using "jbsr".

By default, "jsr XX" will be replaced by "jsr XX(pc)", which is very annoying when you want to determine exactly what code is generated. You can avoid this behavior by using the -S assembler option.

One word about assembler options. The assembler is m68k-atari-mint-as, but it is probably easier to always use the frontend m68k-atari-mint-gcc. If you want to pass the -S option to the assembled by using gcc, you must use the "-Wa" option to forward the option to the assembler. For example :
m68k-atari-mint-gcc -c hw.s -Wa,-S


Same applies with "-Wl" for linker options. For example, if you link your program with :

m68k-atari-mint-gcc *.o -o prog.prg -Wl,--traditional-format


The generated program will contain DRI debugging information instead of GNU. Thus, gdb will be unusable, but you will see the function labels in traditional Atari ST debuggers like MonST. Note that in that case, m68k-atari-mint-strip will be unusable, too.

GCC inline m68k assembly explained (a little)


A simple example is a wrapper around Cconws()(writes null terminated string to output stream):

m68k GAS inline assembly
static __inline__ void Cconws(const char* s) { __asm__ __volatile__ ( "move.l %0,-(%%sp)\n\t" "move.w #9,-(%%sp)\n\t" "trap #1\n\t" "addq.w #6,sp\n\t" : /* outputs */ : "g"(s) /* inputs */ : "d0", "d1", "d2", "a0", "a1", "a2" /* clobbered regs */ ); }


First, the double underscores. "inline" is the same as "inline", but GCC will never complain because it is a nonstandard extension. It is very useful in .h which may be compiled with "-pedantic" flag for example.

The function is declared with "static inline", so it will always be inlined (except when optimization is turned off). "asm" begins an assembly block, "volatile" tells GCC to never remove the contents of the block, even it thinks it is useless.

Parameters:
  • The first parameter of "asm" is the assembly string. Every line must be ended with "\n\t". The "%"(percent sign) character must be escaped as "%%"", so if you choose to prefix register names (not yet mandatory), you must use things like %%d0. Motorola syntax is welcome, as the string is directly transmitted to gas.

  • The second parameter is the list of output variables (not used in this example).

  • The third parameter is the list of the input variables. The letter "g" means general, it can be replaced by a register or a memory variable. (s) is the C variable associated with this input, here it refers to the function parameter "s". This parameter is referred in the assembly string with %0.

  • The fourth parameters is the list of clobbered (trashed) registers. GCC will never try to store data in that registers before calling the assembly block (but it may do it if you forget a register in the clobber list).

Here is another example. It is similar to what is found in <mint/osbind.h>:

example m68k GAS directives
#define SuperFromUser() \ (void*)__extension__ \ ({ \ register long retvalue __asm__("%d0"); \ \ __asm__ __volatile__ \ ( \ "clr.l -(%%sp)\n\t" \ "move.w #0x20,-(%%sp)\n\t" \ "trap #1\n\t" \ "addq.w #6,%%sp\n\t" \ : "=r"(retvalue) /* outputs */ \ : /* inputs */ \ : "%d1", "%d2", "%a0", "%a1", "%a2" /* clobbered regs */ \ ); \ \ retvalue; \ })


This example uses a preprocessor macro and an "extension" block instead of an inline function (the former example one is 'more preferable' approach).

An extension block behaves like an expression (it can be used inside formulas or other expressions), but contains a body with several instructions. The value of the resulting expression is the last expression evaluated in the block.

The variable "retvalue" is declared with "register" and asm("%d0"), so the compiler will always store this variable in the register d0 rather in the memory. You can see the line "retvalue;" at the end of the block. That means: the value of the extension block is what is left in the d0 register after calling the inline assembly block.

Here, the output list is used. It is "=r"(retvalue), "=" indicates it is an output (mandatory here), "r" indicates that this output is a register, and "retvalue" is the associated variable.

Notes:
  • Output variable is not referred inside the assembly block, because it is implicitly filled by "trap #1". If it would be referred, it would be as "%0".
  • If the assembly block contains 3 outputs and 2 inputs, the outputs would be %0, %1 and %2, and the inputs %3 and %4. Inputs are numbered after outputs.

Labels inside the m68k assembly inline code

A declaration of a local label must be a number followed by a colon. But a reference to a local label needs a suffix of f or b, depending on whether you want to look forwards or backwards - i.e. 1f refers to the next 1: label in the forward direction, 1b refers to the label 1: but in backward direction.

local label with backward reference
static __inline__ void sampleFunction(void) { __asm__ __volatile__ ( "1: nop\n\t" "btst.b #39,0xfffC02.w\n\t" "beq.s 1b\n\t" : /* outputs (empty) */ : /* inputs (empty) */ : /* clobbered regs (empty)*/ ); }



If will not supply number as a label, then labels will be global. So if in the two pieces of inline assembly code there will be two identical labels, then compiler will throw an error that there are doubly defined symbols.