http://www.cs.nmsu.edu/~jeffery/courses/370/code-sparc.html
The SPARC, invented by Sun and licensed and manufactured by Sun, T.I., and others, is one of the first widely used commercial RISC architectures. Generating code for it involves understanding the instruction set as well as the syntax of the Sun assembler used to take ascii assembly language code and produce machine code object files.
Assembly code files should end with the suffix ".s"
. Such a file can be assembled to executable code by invoking the C compiler:
% cc -o foo foo.s
Note: running "as foo.s
" will not produce an executable module, it will produce an object module (.o) that requires linking. For examples of the assembly code produced by the C compiler on the Sun, use "cc -S
". Your compiler should behave in the same manner as the standard C compiler, calling the assembler and linker by default. You may wish to leave .s files around after assembly for debugging purposes, rather than deleting them by default.
Sparc architecture has strict alignment requirements on memory access; 16-bit values can only be loaded and stored to even addresses, while 32-bit values can only be loaded and stored to addresses that are multiples of 4.
The Sparc is a load/store architecture with many registers, and hardware support for register mapping which greatly reduces the cost of normal procedure calls. At any one time, there are 32 registers available to machine instructions, although the typical SPARC has upwards of 128 registers on the chip. The assembler's names for registers all begin with a percent sign %
to distinguish them from ordinary symbols. The registers' names are %g0-%g7
, %i0-%i7
, %o0-%o7
, and %l0-%l7
.
The SPARC registers are organized into register windows (also called register files) that define the set of registers that are addressable at any given point during execution. Generally, each time a procedure is called, 16 registers that were visible in the previous procedure become invisible, and 16 new registers become addressable. 8 registers which were visible in the caller as %o0-%o7 become the called routine's %i0-%i8. The register window set is circular and wraps around. When no more registers are available for a call, a register spill occurs, in which a hardware trap to the operating system saves some registers onto the stack in order to make them available for the new procedure. This register window mapping is done by save/restore instructions, and can be avoided for leaf routines that do not call other routines.
The stack grows from high addresses towards low addresses. The stack pointer register is %o6, a.k.a. %sp. The frame pointer register is %i6, a.k.a. %fp. The return value is normally a small offset of %i7, since the current program counter is stored in %o7 by the call instruction. A stack frame has the following structure:
Global identifier id in the source program translates directly to an identifier id in the assembly code generated. Local identifier id translates into an offset from the frame pointer. Be careful with negative offsets, since the memory addressed still refers to bytes extending in a positive direction from the byte referenced. For example, a 4-byte load from %fp-4 gives addresses -4, -3, -2, and -1 from the current frame pointer, not bytes at -4, -5, -6, and -7. A load from %fp+4 would indeed give memory at offsets 4, 5, 6, and 7.
Space for global variables is allocated in a data segment:
.section ".data" global variables .section ".text"
Space for global variables is generated one identifier at a time. An identifier id that occupies n bytes of storage with alignment multiple m is allocated as
.common id, n, m
Examples:
C code assembler directive .section ".data" int x, a[12]; .common x,4,4 .common a,48,4 char y; .common y,1,1 .seg ".text"Code should all be generated in the "text" segment (.seg "text"
). Before starting a portion of code for a function foo, generate.align 4 .global foo .type foo,#function .proc 020 foo:
Code and data can be intermixed, by switching segments back and forth.Parameters
Parameters all occupy space on the stack and are generally pushed from
back to front; the first six parameters are, if they fit into 32 bits,
passed in registers instead. Data smaller than 4 bytes is passed in a
4 byte position on the stack; data larger than 4 bytes per parameter
is longword aligned.
Accessing an actual parameter from within the called function consists
of accessing register%in-1
,
for parameters 1 through 6, or
loading memory [%fp + k], where k is 64 + the offset of the parameter
(64 + 4 * i for parameter i, if all parameters are passed as 4 bytes).Size Conversions
The instruction ``ldsb src, reg'' loads and converts the source byte
in memory into a 32-bit register value. The value is sign-extended
(there is a corresponding unsigned operation).
A corresponding instruction, ``stdb reg, dest'' stores the low byte of
a 32-bit value into a single byte of memory, accomplishing the
conversion in the other direection.Translating Assignment Statements
Note: for simplicity, the operations below are all translated in terms
of 32-bit operands. For character operations a ``b'' is appended to
the instruction; any C language mixed-type arithmetic should result in
explicit size conversions per the preceding section.
Most of these operands are using temporary registers, usually the %o
registers.Global operands
Global values have addresses computed as constants at compile times;
such 32-bit values are loaded into a register
x := y + z | set y, reg1 ld [reg1 ], reg1 set z, reg2 ld [reg2 ], reg2 add reg2 , reg1 set x, reg2 (if x is a global) st reg1 , [reg2 ] (or mark reg1 as holding x) |
x := y + z | ld [%fp + y], reg1 ld [%fp + z], reg2 add reg2 , reg1 st reg1 , [%fp + x] (or mark reg1 as holding x) |
goto L | ba L |
if x op y goto L | ld x, reg1 ld y, reg2 cmp reg1 , reg2 bcc L nop |
e | equal |
ne | not equal |
l | less than |
le | less than or equal |
ge | greater than or equal |
g | greater |
x[i] | set i, reg1 ld [reg1 ], reg1 sll reg1 ,2,reg1 set x, reg2 ld [reg2 + reg1] |
x[i] | set i, reg1 ld [reg1 ], reg1 sll reg1 ,2,reg1 add %fp,x,reg2 add reg1 ,reg2 ,reg1 ld [reg2 + reg1 ] |
!#PROLOGUE# 0 save %sp,-n,%sp !#PROLOGUE# 1
Sun compilers also store something in %g1, GNU C does not.
param i | set i,%o2 ld [%o2], %o2 set c,%o1 ldsb [%o1], %o1 call f nop |
return | ret restore |
return x | ld [%fp+x],%i0 & ret & restore |