GTC supports a number of extensions, designed to take advantage of the TI platform, or simply to maintain compatibility with existing programs.
See Generalized Lvalues.
Note however that weird constructs like (a,b)+=5
are rejected by GTC -- commas are not considered legal lvalues.
See Compound Literals (Cast Constructors). There is some difference in the handling of static storage duration though, see Differences between GTC and TIGCC.
See Specifying Attributes of Functions, however not all of the specifiers are meaningful in GTC.
incbin
directiveIt is sometimes convenient to include binary files directly into your program, for example when using sprites. The incbin
directives allows you to do just that, without having to convert your data to a decimal or hexadecimal header (which can be memory-consuming on a TI).
incbin
to declare a C arrayYou can use incbin
to initialize the contents of a C array:
// The following declares a sprite using normal C initializers
short sprite1[] = {
0xFFFF, 0x8008, 0x8008, 0xFFFF,
0xFF00, 0x8800, 0x8800, 0x8800,
0x00FF, 0x0088, 0x0088, 0x0088,
0xFFFF, 0x8008, 0x8008, 0xFFFF,
};
// The following declares a sprite using incbin
,
// reading the contents from the file sprite.bin
short sprite2[] = incbin "sprite.bin";
The first declaration creates an array of 16 shorts containing the hexadecimal numbers provided.
The second declaration creates an array of 16 shorts whose representation in memory will be the same as sprite.bin
.
Note that for the sake of portability, import_binary
can be a useful
alternative, although there are several caveats: you will have to manually
declare the size of the array if sizeof
is to be used, and you cannot use it
to declare a static array inside a function.
asm{}
statementGTC allows you to program in assembly, with a syntax very close to that of a68k. In fact, it is powerful enough to write full assembly programs using only GTC!
asm{}
statement in the codeasm{}
statementsYou can insert assembly code at the global level, for example:
asm { add_3_and_multiply_by_5: add.w #3,d0 mulu #5,d0 rts };
This creates an assembly function named add_3_and_multiply_by_5
that takes an input argument in d0
, adds 3 to it, multiplies it by 5, and returns the result in d0
.
To use such a function in C code, you need to prototype it:
short add_3_and_multiply_by_5(short x);
This tells GTC that your function takes a short
named x
as an input, and outputs a short
. Because of the calling convention, x
will be placed in d0
: see default calling convention for more information on where input arguments to assembly functions will be located.
asm{}
statementsYou can also insert assembly code within a function. This has the advantage over global assembly functions to eliminate the overhead associated with a function call.
void do_something(char *command) { if (!strcmp(command,"off")) asm { trap #4 }; else printf("unknown command!\n"); }
This functions turns off the calculator when command
is "off"
, by calling trap #4
.
asm{}
statementAn asm{}
statement is comprised of a set of declarations between the outer {
and }
braces.
A declaration can be:
moveq #123,d0
my_function:
dc.w 123,456,789
or incbin "data.bin"
You must separate different declarations either:
;
) -- however you should absolutely avoid doing this outside #define
, as the behaviour may change some day
asm{}
interpretation rulesThe behaviour of GTC is indeed very close to that of a68k
. Like a68k
, GTC tries to optimize instructions when it is possible, for example add.w #3,d0
will be optimized to addq.w #3,d0
. Like a68k
, you don't need to add a :
after label names: however, while with a68k
you can only do so if the label is placed on the first char of the line, with GTC you can do so any time as long as the label does not correspond to an assembly instruction.
The most notable difference is perhaps that, where a68k
uses specific commands like equ
and equr
to define macros, GTC simply uses the C preprocessor. This is very powerful, as it allows you to define constants just once and reuse them in C code. You can also use normal C conditionals like #if
or #ifdef
, making conditional compilation easier.
You can also interface your code with C much more easily, for example you have access to the sizeof
operator:
int table[] = { 1,2,3,4 }; asm { negate_table: lea table,a0 moveq #sizeof(table)/2-1,d0 \loop neg.w (a0)+ dbra d0,\loop rts };
After execution of negate_table
, table
will be equal to { -1,-2,-3,-4 }
.
Note the \
in \loop
: this means that the label is local. However, this notion differs slightly from that of a68k
: while the label is only valid between the surrounding two global labels in a68k
, the label is valid throughout the asm{}
statement in GTC. It is often more convenient, as this example shows:
asm { \return_with_error moveq #-1,d0 rts my_function: addq.w #5,d0 bmi.s \return_with_error tst.w d1 bmi.s \return_with_error ... very long code (longer than 128 bytes) ... rts };
In this example, \return_with_error
is placed before my_function
because it allows the branches to \return_with_error
to be short branches (bmi.s
, 2 bytes) rather than long branches (bmi.w
, 4 bytes). If GTC followed the rules of a68k
, then it would require \return_with_error
to be a global, which can be inconvenient if you have lots of these. Instead, it allows you to structurally divide your program in logical asm{}
blocks.
asm{}
statementsBecause asm{}
statements rely on the C compiler architecture of GTC, there are lots of nice features, for example:
#define USE_KERNEL #include <std.h> asm { _main: pea "Hello, world!"(pc) jsr ST_helpMsg addq.l #4,a7 rts };
What's most interesting in all this is that the C library required no modification whatsoever to allow using ROM calls in such a way: ROM calls are not defined twice, and there isn't even a conditional statement acting differently inside asm{}
code and inside C code!
This is possible because the assembler was able to interpret the C construct corresponding to ST_helpMsg
.
asm{}
special operator: __c__
This is an operator reserved to operands of instructions in asm{}
statements.
The reason why this operator exists is simple: assembly and C each have different arithmetic rules. For example, if you have the following code:
int table[] = { 1,2,3,4 };
then, while in C table+2
will designate the address of table[2]
(the number 3
), in assembly table+2
will designate the address of table[0]
plus 2 bytes, which happens to be the address of table[1]
.
So when you write:
asm { load_address: lea table+2(pc),a0 rts };
then, as you would expect from assembly code, table+2
corresponds to the address given by assembly arithmetic rules.
This works with #define
statements too:
asm { load_address: #define my_item table+2 lea my_item(pc),a0 rts };
But GTC allows you to do much more: you may want to access information that requires access to the full typing system of C, not just the addresses of different objects.
The way you can access this typing system is by the __c__
operator: if you write
asm { load_address: lea __c__(table+2)(pc),a0 rts };
then table+2
will not be interpreted according to assembly rules, but according to C rules: that is, __c__(table+2)
is the adress of table[2]
.
GTC goes one step further, by automatically enclosing #define
s written as part of C code in the __c__
operator:
int table[] = { 1,2,3,4 }; #define third_item &table[2] // note: table+2 would give the same results int *c_load_address() { return third_item; } asm { load_address: lea third_item(pc),a0 rts };
Here both c_load_address()
and load_address()
do the same thing, without ever having to define third_item
twice!
@@
prefixThe prefix @@
can be prepended to any identifier to prevent it from being expanded by the preprocessor.
For example, @@MY_SYMBOL
will expand to MY_SYMBOL
even if MY_SYMBOL
has been #define
d to expand to 0x1234
.
This is not very useful in normal C code, but it can be useful in conjunction with other GTC extensions, mainly asm{}
statements and pre-compiled headers.