Techno-Plaza
Site Navigation [ News | Our Software | Calculators | Programming | Assembly | Downloads | Links | Cool Graphs | Feedback ]
 Main
   Site News

   Our Software

   Legal Information

   Credits

 Calculators
   Information

   C Programming

   Assembly

     Introduction

     Keyboard Input

     Basic Graphics

     C & Assembly

       Part I

       Part II

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

TIGCC Assembly Lessons

TIGCC Assembly Programming Lessons

Lesson 4: Integrating C and Assembly

Step 4 - C Calling Conventions

We have seen plenty of examples in the first three lessons on how to call external functions in assembly. We have used the AMS jump table to call the system functions, and have used some TIGCC functions from the TIGCC library as well. Up till now however, we have not discussed in detail how we knew to use these functions.

We have used the phrase 'calling conventions' a lot, but what does it mean? Simply put, a call means to use a function. The calling conventions determine how we call that function. For our purposes, this means, how do we find the address of the function, and how do we pass arguments to this function.

Each system defines its own calling conventions. There are a lot of common threads, but no real standards. When using TIGCC, we even get multiple calling conventions. So we need to discuss how to identify a functions calling conventions, and how to use them.

We have already seen examples using all the various calling conventions. The AMS functions have their arguments pushed on the stack in reverse order, and the address is located in the system jump table. The TIGCC library functions written in C use regparm(4) conventions. This means they get their arguments in the registers, d0-d3 and a0-a3 for data and addresses, respectively. The TIGCC library functions written in assembly pass their arguments in registers, but not necessarily according to regparm(4) conventions. The final one, functions you write in C will use stkparm. This means the arguments are pushed on the stack in reverse order. This is the same as the AMS functions, and is the default calling convention of gcc.

So, how do we identify the various calling conventions? There are several clues in the TIGCC docs and header files. In the TIGCC docs for a function, saw DrawStr for example, the upper right corner of the page says ROM Call. This means it is an AMS function. So to call DrawStr, we push its arguments on the stack in reverse order, and use the jump table to get the address.

Another function, atoi, is part of the TIGCC library. If you look in the TIGCC docs, we see at the top-right that is says tigcc.a. This means it is a TIGCC library function. But this does not tell us its calling conventions. For that, we need to look in the header file that has the prototype. Also near the top-right is the header file, in this case stdlib.h. Unless you changed the path, in Windows, this header file is located in C:\Program Files\TIGCC\Includes\C. Open the stdlib.h file and search for atoi. You'll note it has an __ATTR_LIB_C__ at the end. This means it uses regparm(4) conventions.

Let's try another one, the atexit function. It's easy enouh to find because it's also defined in stdlib.h. At the end of this function, we see it has __ATTR_LIB_ASM__. We will cover the exceptions in a moment, but this usually signifies stkparm, i.e. that the parameters are pushed on the stack in reverse order.

Let's see one of those exceptions now. Another TIGCC library function is the _rowread function, which we have used before. It is defined in the kbd.h header file. If you look for _rowread, you will see it has __ATTR_LIB_ASM__ defined. But, we do not pass the parameters on the stack. If you look in the prototype, after the short it says asm("d0"). This means the first (and only in this case) argument is passed in the d0 register.

I don't have an example here, because we have already seen examples for all of these in our previous lessons. Sprite16 is an example of an __ATTR_LIB_C__ function we have used. DrawStr is an AMS function. _rowread is an __ATTR_LIB_ASM__ function that specifies the registers explicitly. Maybe we haven't seen a stkparm __ATTR_LIB_ASM__, but it's just like the AMS functions.

Now, what about the addresses? We've already seen how to use the jump table. What about the TIGCC library functions? Well, that's easy. We can use bsr [name of function] for any TIGCC library function. This is because it will become part of our program.

There is one more note I want to make. If you look up a 'function' in the TIGCC docs, and it says macro at the top-right, this means it is not a function at all. You cannot call macros.

Step 5 - The Inline Assembler

The final method for integrating C and assembly is the inline assembler. You can actually embed assembly within your C code. To do this, simply use put asm("opcodes") into your program. You can separate multiple opcodes with semi-colons, or newlines. You can even use C variables.

Let's take a quick look at a couple examples. Start TIGCC and create a new project. Make a new C source file called romcall. We will not need to make an assembly file this time.

romcall.c


#include <tigcclib.h>

#define NGETCHX 0x51

static void clear_screen(void) {
    asm("movea.l 0xc8,%a0
         movea.l 1656(%a0),%a0
         jsr (%a0)");
}

static void romcall(short int number) {
    number <<= 2;

    asm("movea.l    0xc8,%%a0
         adda.l     %[number],%%a0
         movea.l    (%%a0),%%a0
         jsr        (%%a0)"
        : // no output
        : [number] "g" (number)
        : "a0"
       );
}

void _main(void) {
    clear_screen();
    romcall(NGETCHX);
}

Step 5a - Program Analysis

Build the program and sent it to TiEmu. I didn't make a screenshot, because the program simply clears the screen and waits for a key press.

The clear_screen function is pretty simple. We use three opcodes to call the ClrScr AMS function. In case you were wondering where the number came from, ClrScr is ROM Call 0x19E, and 4 * 0x19E = 1656 decimal.

The romcall function is a little more complex. The assembly is easy enough. The only caveat is that we can't simply put number beside the movea function, because number is a variable, not a constant. This means we can't use it as a displacement value. number(%a0) has no meaning in assembly. So we'll just add it to the address of the jump table, then dereference to get the function. Because we gave the romcall function the ngetchx ROM call number, this is the AMS function we are calling.

Now you may be wondering about the double-percent signs in front of the registers. This is required to differentiate it from the C variables we are going to use, which also require you to prepend a %. We can use small numbers, like %0, %1, or we can give them names. We didn't have to use number as our name in the inline assembly, but it seemed appropriate.

At the end of our assembly instructions, we have three fields separated by a : (colon). The first field marks our output variables, i.e., those fields that will have their value changed by the assembly. The second field is for our input variables. In this case, we are going to use the number function argument in the assembly, so we need to give it as an input. The third field is for our clobber registers. If we are going to change a register, we need to tell gcc about it. This is because it may be using that register. It won't matter in this example, but there will come a time when it does.

The [number] specifies the name. We didn't have to use [number] here, as I've already said. The (number) is for the variable. The middle parameter is how this variable should be used. GCC will have to generate assembly in order to address our C variables. "g" stands for general, which means it can be a data register, address register, or memory operand. You may have to play with this value if gcc generates invalid code. Take a look at the Inline Assembler section in the TIGCC docs for more information on what kinds of values are possible.

Step 6 - Conclusions

I hope you have taken something useful from this lesson. There is a lot more to learn about the inline assembler, but understanding the basics is the first step. The TIGCC docs has a lot more information, and you can google a ton on this as well.

In this lesson, you learned about calling conventions, how to integrate C and assembly functions, and how to use the inline assembler. You should now have the tools you need to optimize any C program where needed.

 


Lesson 1: Introduction to TIGCC Assembly Programming
Questions or Comments? Feel free to contact us.


 

Copyright © 1998-2007 Techno-Plaza
All Rights Reserved Unless Otherwise Noted

Get Firefox!    Valid HTML 4.01!    Made with jEdit