|[ < ]||[ > ]||[ << ]||[ Up ]||[ >> ]||[Top]||[Contents]||[Index]||[ ? ]|
GNU lightning's instruction set was designed by deriving instructions that closely match those of most existing RISC architectures, or that can be easily syntesized if absent. Each instruction is composed of:
The second and third field are separated by an underscore; thus,
examples of legal mnemonics are
addr_i (integer add, with three
register operands) and
muli_l (long integer multiply, with two
register operands and an immediate operand). Each instruction takes
two or three operands; in most cases, one of them can be an immediate
value instead of a register.
GNU lightning supports a full range of integer types: operands can be 1, 2 or 4 bytes long (64-bit architectures might support 8 bytes long operands), either signed or unsigned. The types are listed in the following table together with the C types they represent:
c signed char uc unsigned char s short us unsigned short i int ui unsigned int l long ul unsigned long f float d double p void *
Some of these types may not be distinct: for example, (e.g.,
is equivalent to
i on 32-bit machines, and
substantially equivalent to
There are at least seven integer registers, of which six are
general-purpose, while the last is used to contain the stack pointer
SP). The stack pointer can be used to allocate and access local
variables on the stack (which is supposed to grow downwards in memory
on all architectures).
Of the general-purpose registers, at least three are guaranteed to be
preserved across function calls (
V2) and at least three are not (
R2). Six registers are not very much, but this
restriction was forced by the need to target CISC architectures
which, like the x86, are poor of registers; anyway, backends can
specify the actual number of available caller- and callee-save
In addition, there is a special
RET register which contains
the return value. You should always remember, however, that writing
this register could overwrite either a general-purpose register or
an incoming parameter, depending on the architecture.
There are at least six floating-point registers, named
FPR5. These are separate from the integer registers on
all the supported architectures; on Intel architectures, the
register stack is mapped to a flat register file.
The complete instruction set follows; as you can see, most non-memory operations only take integers, long integers (either signed or unsigned) and pointers as operands; this was done in order to reduce the instruction set, and because most architectures only provide word and long word operations on registers. There are instructions that allow operands to be extended to fit a larger data type, both in a signed and in an unsigned way.
addxoperations must directly follow
subc; otherwise, results are undefined.
addr i ui l ul p f d O1 = O2 + O3 addi i ui l ul p O1 = O2 + O3 addxr i ui l ul O1 = O2 + (O3 + carry) addxi i ui l ul O1 = O2 + (O3 + carry) addcr i ui l ul O1 = O2 + O3, set carry addci i ui l ul O1 = O2 + O3, set carry subr i ui l ul p f d O1 = O2 - O3 subi i ui l ul p O1 = O2 - O3 subxr i ui l ul O1 = O2 - (O3 + carry) subxi i ui l ul O1 = O2 - (O3 + carry) subcr i ui l ul O1 = O2 - O3, set carry subci i ui l ul O1 = O2 - O3, set carry rsbr i ui l ul p f d O1 = O3 - O2 rsbi i ui l ul p O1 = O3 - O2 mulr i ui l ul f d O1 = O2 * O3 muli i ui l ul O1 = O2 * O3 hmulr i ui l ul O1 = high bits of O2 * O3 hmuli i ui l ul O1 = high bits of O2 * O3 divr i ui l ul f d O1 = O2 / O3 divi i ui l ul O1 = O2 / O3 modr i ui l ul O1 = O2 % O3 modi i ui l ul O1 = O2 % O3 andr i ui l ul O1 = O2 & O3 andi i ui l ul O1 = O2 & O3 orr i ui l ul O1 = O2 | O3 ori i ui l ul O1 = O2 | O3 xorr i ui l ul O1 = O2 ^ O3 xori i ui l ul O1 = O2 ^ O3 lshr i ui l ul O1 = O2 << O3 lshi i ui l ul O1 = O2 << O3 rshr i ui l ul O1 = O2 >> O3(1) rshi i ui l ul O1 = O2 >> O3(2)
negr i l f d O1 = -O2 notr i ui l ul O1 = ~O2
The conditions given below are for the standard behavior of C, where the "unordered" comparison result is mapped to false.
ltr i ui l ul p f d O1 = (O2 < O3) lti i ui l ul p O1 = (O2 < O3) ler i ui l ul p f d O1 = (O2 <= O3) lei i ui l ul p O1 = (O2 <= O3) gtr i ui l ul p f d O1 = (O2 > O3) gti i ui l ul p O1 = (O2 > O3) ger i ui l ul p f d O1 = (O2 >= O3) gei i ui l ul p O1 = (O2 >= O3) eqr i ui l ul p f d O1 = (O2 == O3) eqi i ui l ul p O1 = (O2 == O3) ner i ui l ul p f d O1 = (O2 != O3) nei i ui l ul p O1 = (O2 != O3) unltr f d O1 = !(O2 >= O3) unler f d O1 = !(O2 > O3) ungtr f d O1 = !(O2 <= O3) unger f d O1 = !(O2 < O3) uneqr f d O1 = !(O2 < O3) && !(O2 > O3) ltgtr f d O1 = !(O2 >= O3) || !(O2 <= O3) ordr f d O1 = (O2 == O2) && (O3 == O3) unordr f d O1 = (O2 != O2) || (O3 != O3)
extboth of them must be registers, while
movaccepts an immediate value as the second operand.
movi, the other instructions are applied
between operands of different data types, and they need two
data type specifications. You can use
extr to convert between
integer data types, in which case the first must be smaller in size
than the second; for example
extr_c_ui is correct while
extr_ul_us is not. You can also use
extr to convert
an integer to a floating point value: the only available possibilities
extr_i_d. The other instructions
convert a floating point value to an integer, so the possible
movr i ui l ul p f d O1 = O2 movi i ui l ul p f d O1 = O2 extr c uc s us i ui l ul f d O1 = O2 roundr i f d O1 = round(O2) truncr i f d O1 = trunc(O2) floorr i f d O1 = floor(O2) ceilr i f d O1 = ceil(O2)
Note that the order of the arguments is destination first, source second as for all other GNU lightning instructions, but the order of the types is always reversed with respect to that of the arguments: shorter---source---first, longer---destination---second. This happens for historical reasons.
hton us ui Host-to-network (big endian) order ntoh us ui Network-to-host order
ldaccepts two operands while
ldxaccepts three; in both cases, the last can be either a register or an immediate value. Values are extended (with or without sign, according to the data type specification) to fit a whole register.
ldr c uc s us i ui l ul p f d O1 = *O2 ldi c uc s us i ui l ul p f d O1 = *O2 ldxr c uc s us i ui l ul p f d O1 = *(O2+O3) ldxi c uc s us i ui l ul p f d O1 = *(O2+O3)
staccepts two operands while
stxaccepts three; in both cases, the first can be either a register or an immediate value. Values are sign-extended to fit a whole register.
str c uc s us i ui l ul p f d *O1 = O2 sti c uc s us i ui l ul p f d *O1 = O2 stxr c uc s us i ui l ul p f d *(O1+O2) = O3 stxi c uc s us i ui l ul p f d *(O1+O2) = O3
pushr i ui l ul p push O1 on the stack popr i ui l ul p pop O1 off the stack
prepare i f d pusharg c uc s us i ui l ul p f d getarg c uc s us i ui l ul p f d arg c uc s us i ui l ul p f d
Of these, the first two are used by the caller, while the last two
are used by the callee. A code snippet that wants to call another
procedure and has to pass registers must, in order: use the
prepare instruction, giving the number of arguments to
be passed to the procedure (once for each data type); use
pusharg to push the arguments in reverse order;
finish (explained below) to
perform the actual call.
getarg are used by the callee.
arg is different from other instruction in that it does not
actually generate any code: instead, it is a function which returns
a value to be passed to
getarg.(3) You should call
arg as soon as possible, before any function call or, more
easily, right after the
(which are treated later).
getarg accepts a register argument and a value returned by
arg, and will move that argument to the register, extending
it (with or without sign, according to the data type specification)
to fit a whole register. These instructions are more intimately
related to the usage of the GNU lightning instruction set in code
that generates other code, so they will be treated more
specifically in Generating code at run-time.
You should observe a few rules when using these macros. First of all, it is not allowed to call functions with more than six arguments; this was done to simplify and speed up the implementation on architectures that use registers for parameter passing.
You should not nest calls to
prepare, nor call zero-argument
functions (which do not need a call to
prepare) inside a
prepare/finish block. Doing this
might corrupt already pushed arguments.
You cannot pass parameters between subroutines using the six general-purpose registers. This might work only when targeting particular architectures.
On the other hand, it is possible to assume that callee-saved registers
R2) are not clobbered by another dynamically
generated function which does not use them as operands in its code and
which does not return a value.
arg, these also return a value which, in this case, is to be used to compile forward branches as explained in Fibonacci numbers. They accept a pointer to the destination of the branch and two operands to be compared; of these, the last can be either a register or an immediate. They are:
bltr i ui l ul p f d if (O2 < O3) goto O1 blti i ui l ul p if (O2 < O3) goto O1 bler i ui l ul p f d if (O2 <= O3) goto O1 blei i ui l ul p if (O2 <= O3) goto O1 bgtr i ui l ul p f d if (O2 > O3) goto O1 bgti i ui l ul p if (O2 > O3) goto O1 bger i ui l ul p f d if (O2 >= O3) goto O1 bgei i ui l ul p if (O2 >= O3) goto O1 beqr i ui l ul p f d if (O2 == O3) goto O1 beqi i ui l ul p if (O2 == O3) goto O1 bner i ui l ul p f d if (O2 != O3) goto O1 bnei i ui l ul p if (O2 != O3) goto O1 bunltr f d if !(O2 >= O3) goto O1 bunler f d if !(O2 > O3) goto O1 bungtr f d if !(O2 <= O3) goto O1 bunger f d if !(O2 < O3) goto O1 buneqr f d if !(O2 < O3) && !(O2 > O3) goto O1 bltgtr f d if !(O2 >= O3) || !(O2 <= O3) goto O1 bordr f d if (O2 == O2) && (O3 == O3) goto O1 bunordr f d if !(O2 != O2) || (O3 != O3) goto O1 bmsr i ui l ul if O2 & O3 goto O1 bmsi i ui l ul if O2 & O3 goto O1 bmcr i ui l ul if !(O2 & O3) goto O1 bmci i ui l ul if !(O2 & O3) goto O1(4) boaddr i ui l ul O2 += O3, goto O1 on overflow boaddi i ui l ul O2 += O3, goto O1 on overflow bosubr i ui l ul O2 -= O3, goto O1 on overflow bosubi i ui l ul O2 -= O3, goto O1 on overflow
retwhich has none; the difference between
calliis that the latter does not clean the stack from pushed parameters (if any) and the former must always follow a
prepareinstruction. Results are undefined when using function calls in a leaf function.
calli (not specified) function call to O1 callr (not specified) function call to a register finish (not specified) function call to O1 finishr (not specified) function call to a register jmpi/jmpr (not specified) unconditional jump to O1 prolog (not specified) function prolog for O1 args leaf (not specified) the same for leaf functions ret (not specified) return from subroutine retval c uc s us i ui l ul p f d move return value to register
Like branch instruction,
jmpi also returns a value which is to
be used to compile forward branches. See section Fibonacci numbers.
As a small appetizer, here is a small function that adds 1 to the input
int). I'm using an assembly-like syntax here which
is a bit different from the one used when writing real subroutines with
GNU lightning; the real syntax will be introduced in See section Generating code at run-time.
incr: leaf 1 in = arg_i ! We have an integer argument getarg_i R0, in ! Move it to R0 addi_i RET, R0, 1 ! Add 1, put result in return value ret ! And return the result
And here is another function which uses the
printf function from
the standard C library to write a number in hexadecimal notation:
printhex: prolog 1 in = arg_i ! Same as above getarg_i R0, in prepare 2 ! Begin call sequence for printf pusharg_i R0 ! Push second argument pusharg_p "%x" ! Push format string finish printf ! Call printf ret ! Return to caller
|[ < ]||[ > ]||[ << ]||[ Up ]||[ >> ]||[Top]||[Contents]||[Index]||[ ? ]|