Linking C with ASM on the Jaguar(Boz's Jagdev Environment)

Let's get coding!

Moderator: a31chris

Post Reply
User avatar
a31chris
Jaguar MOD
Posts: 896
Joined: Mon Apr 01, 2013 7:09 am

Linking C with ASM on the Jaguar(Boz's Jagdev Environment)

Post by a31chris » Sun Feb 23, 2014 6:45 pm

Linking C with ASM by SubQMod

As per boz's suggestion, here is a small tutorial on parameter passing between C and Assembler. Just wrote it in 20 minutes so apologies if it doesn't make sense. Please ask away if you have questions:

Code: Select all

Parameter Passing from C to ASM Using Belboz's Jagdev Environment Toolset
-------------------------------------------------------------------------

This took me a little while to figure out so it makes sense to pass on
what I have learnt.

When writing Jaguar code that has both C and Assembler using this environment
you will be using SMAC to compile the assembler and VBCC to compile the 
code.  Using these tools are not covered here but it is important to understand
that this tutorial relates to this specific setup.  So lets begin...

Lets say that you have written an optimised 68000 assembler routine to display
a string on screen but your core code is in C so you want to be able to pass
the X and Y coordinate to display at as well as the memory address of the 
string to display.  

Although not necessary to compile, it is good practise to have a forward 
decleration of this function in your C code before you use it.  It also helps
the C compiler to ensure that correct variables are passed to the routine in 
question.  The forward decleration below shows what this may look like:


extern void foo_str(unsigned long int x, unsigned short int y, char *str);


Remember, this function does not exist in C code and is just a 68000 routine
in an assembly (.s) file.

Using this function in C is, as I am sure you know, a simple task.  For example,
lets call it from func1():


void func1(void) {
   char *string = "Hello World!\0";

   foo_str(10, 12, string);
}


Thats the C end all sorted, no black magic or dark arts, just simple C coding.

Now, the fun stuff, reading in those parameters.  But first, lets understand 
where the passed parameters, X, Y and string, have gone.  Just before the 
routine was called these parameters were placed on the stack at the top of RAM
and this is where we need to get them from.  Each parameter, regardless of
whether it is a byte (8-Bit), short (16-Bit) or long (32-bit) are stored in 
long words (32-bit) on the stack.  I want to avoid all the mechanics of stack
management in this tutorial, as thankfully much of it is managed for us.  The 
key information to know is that we will be using the A7 register as this has
the base address of the stack. Each parameter on the stack has a byte index, 
so combining the A7 address with the parameter index we can obtain our 
parameters.

All parameter indexes start at $4 (hex) with each parameter being stored four
further bytes (log word) later.  So with our example in mind, the parameter
indexes are:


Parameter   Index

x           $4
y           $8
str         $c


The index increases by 4 bytes for each parameter passed, for example if we 
had a function passing six parameters we would have the following indexes:


Parameter   Index

param1      $4
param2      $8
param3      $c
param4      $10
param5      $14
param6      $18


Now that we know what indexes we can find our parameters at we need to 
combine this with the A7 stack register.  So, in our code, if we want to
get the contents of the first parameter and place them into register d0
we would type:


move.l   ($4,A7),d0


Its that simple.  Well, not quite;)

You will have noticed earlier that our second parameter was a short integer
and if we try to access it from index $8 we will get zero.  Because the stack
is made up purely of long integers we need to add to the index to obtain
short integers.  Parameter 2's base index is $8, we want the short integer 
stored there so we need to add $2 to the base index.  Therefore, to load
our second parameter into d1, we would type;


move.w   ($a,A7),d1


Our third parameter, is an address to a string of characters.  All addresses
are long integers so we can read directly from the parameter index, as such;


move.l   ($c,A7),a0


So, our 68000 routine may look something like this;


_foo_str:
   move.l   ($4,A7),d0    ; Get X Parameter
   move.w   ($a,A7),d1    ; Get Y Parameter
   move.l   ($c,A7),a0    ; get String Address

  ; .... Do Some Processing with this Data ...

   rts


It is VERY IMPORTANT to remember to preceed your function label with an 
underscore so that the Linker can correctly marry up C and Assembler 
functions.

This is where the story should end, and when I first was playing around with 
this I though so too.  But I forgot one key issue.  When calling assembler
routines from C, YOU MUST ensure that your assembler function saves all the
registers you use in your assembler function to the stack before processing 
and return them after processing is completed.  

THIS AFFECTS THE STACK INDEXES !!!!!!! ARGGGGHHH !!!!

But it is relatively straight forward to understand.  In our example, code 
above we used registers d0, d1 and a0 in our routine.  First, lets ensure
these get pushed to the stack at the start of the function and popped off
at the end.  Our code would look like this:


_foo_str:
   movem.l  d0-d1/a0,-(sp); Save registers

   move.l   ($4,A7),d0    ; Get X Parameter
   move.w   ($a,A7),d1    ; Get Y Parameter
   move.l   ($c,A7),a0    ; get String Address

  ; .... Do Some Processing with this Data ...

   movem.l  (sp)+,d0-d1/a0; Restore registers
   rts


The problem you may have noticed is that if our passed C parameters are on
the stack and now are saved registers are also then the indexes we use to 
access our parameters are wrong.  If you did, then you are correct :)

To resolve this issue we need to add the number of bytes of stack space used
for saving our registers to the base parameter indexes.  In this example, we
have saved (pushed) three registers to the stack, each register being a long
integers, so four bytes long each.  Therefore we have increased the stack by
12 bytes (or $c in hex).

To make sure we can access our parameters we need to add the stack increase
to the base indexes, so our code would now look like this;


_foo_str:
   movem.l  d0-d1/a0,-(sp); Save registers

   move.l   ($10,A7),d0   ; Get X Parameter    ($4 + $c)
   move.w   ($16,A7),d1   ; Get Y Parameter    ($a + $c)
   move.l   ($18,A7),a0   ; get String Address ($c + $c)

  ; .... Do Some Processing with this Data ...

   movem.l  (sp)+,d0-d1/a0; Restore registers
   rts


This covers the basics of parameter passing in C.  I have not covered
return addresses here and will probably do so soon.

Hopefully, this kinda makes some sense.  Am happy to answer any questions
you may have....
To follow on from my brief tutorial of parameter passing I thought I would highlight a real world example, displaying a sprite on screen using the blitter. In C, the forward decleration of the routine is:

Code: Select all

/*
   x          X Pixel Position to Display Sprite on Screen
   y          Y Pixel Position to Display Sprite on Screen
   width      Width of Sprite
   height     Height of Sprite
   data       Address of Sprite Data
   wflag      Width Flag for Blitter Routine (e.g WID16)
   trans      Transparent color 0 flag
*/

#define TRANS     1                                   /* Transparent or Solid Color Sprites (color=black) */
#define SOLID     0                             

extern void sprite(unsigned short int x, unsigned short int y,
                   unsigned short int width, unsigned short int height, 
                   char *data, unsigned int wflag, unsigned int trans);
Calling this in the C program might look like this:

Code: Select all

void func1(void) {
   sprite(140, 112, 48, 28, dk01, WID48, SOLID);      /* Display Donkey Kong */
}
The sprite routine in assembler follows. It is pretty well commented so I will let you review and I'll be happy to answer any questions you might have...

Code: Select all

;---------------------------------------------------------------------------------------------------
; Procedure: Display a Sprite
; Registers: d3 - Height and Width of Sprite
;            d4 - Scratch Register
;            d5 - X,Y Screen Location
;            d6 - Scratch Register
;---------------------------------------------------------------------------------------------------

_sprite:
   movem.l  d3-d6,-(sp)                              ; Save registers
.wait:                                                
   move.l   B_CMD,d6                                 ; Wait for blitter to finish
   ror.w    #1,d6                                    ; Check if blitter is idle
   bcc.b    .wait                                    ; Bit was clear -> busy

   move.l   _activebuf,d6                            ; Obtain active buffer flag
   bne      .buf1
   move.l   #(_videobuffers + (BMP_WIDTH*BMP_HEIGHT)),d6; Set address for buf1 if buf0 active
   bra      .setaddr
.buf1:                                                
   move.l   #_videobuffers,d6                        ; Set address for buf0 if buf1 active
.setaddr:
   move.l   d6,A1_BASE                               ; Point to destination address
   move.l   #PIXEL8|XADDPIX|WID256|PITCH1,A1_FLAGS   ; Set destination flags, 256=size of vid buf
   move.l   ($28,a7),d4                              ; Get address generation width from stack
   or.l     #PIXEL8|XADDPIX|PITCH1,d4                ; Add additional source data flags
   move.l   d4,A2_FLAGS                              ; Set source data flags
   move.w   ($16,a7),d5                              ; Get screen x value from stack
   swap     d5                                       ; Swap register
   move.w   ($1a,a7),d5                              ; Get screen y value from stack
   move.l   d5,A1_PIXEL                              ; Set pixel destination x,y position
   move.w   ($22,a7),d3                              ; Get sprite height from stack
   swap     d3
   move.w   ($1e,a7),d3                              ; Get sprite width from stack
   move.l   d3,B_COUNT                               ; Set height and width counters
   moveq    #0,d6                                    ; Zero register
   move.l   d6,A2_PIXEL                              ; Set pixel data x,y position to 0,0
   move.w   #1,d6                                    ; Set single line step
   swap     d6                                       ; In upper word of long
   neg.w    d3                                       ; Negate pixel data width
   move.w   d3,d6                                    ; Set pixel data width in low word
   move.l   d6,A1_STEP                               ; Set destination stepping
   move.l   d6,A2_STEP                               ; Set source stepping
   move.l   ($24,a7),d6                              ; Load sprite data address
   move.l   d6,A2_BASE                               ; Set source data address to sprite address
   move.l   ($2c,A7),d4
   bne      .transparent                             ; Get solid/transparent then start the blitter
   move.l   #SRCEN|DSTEN|UPDA1|UPDA2|LFU_REPLACE,B_CMD 
   jmp      .exit
.transparent:
   move.l   #SRCEN|DSTEN|UPDA1|UPDA2|LFU_REPLACE|BKGWREN|DCOMPEN,B_CMD
.exit:
   movem.l  (sp)+,d3-d6                              ; Restore registers
   rts
Its important to note that this isn't necessarily the right or wrong way to do it, its just a way I find works for me. It should also provide a good insight to various methods I personally use.

Enjoy
What came after the Jaguar was the PS1 which for all it's greatness, ushered in corporate development and with it the bleached, repetitive, bland titles which for the most part we're still playing today. - David Wightman

txg/mnx
3DO ZERO USER
Posts: 18
Joined: Wed May 21, 2014 6:52 pm

Re: Linking C with ASM on the Jaguar(Boz's Jagdev Environmen

Post by txg/mnx » Tue Jul 22, 2014 2:18 pm

Question

How do you return a status code from assembly to C ?

Did I miss that part ?

User avatar
a31chris
Jaguar MOD
Posts: 896
Joined: Mon Apr 01, 2013 7:09 am

Re: Linking C with ASM on the Jaguar(Boz's Jagdev Environmen

Post by a31chris » Sat Sep 07, 2024 11:23 pm

txg/mnx wrote:
Tue Jul 22, 2014 2:18 pm
Question

How do you return a status code from assembly to C ?

Did I miss that part ?
Since people in the Jag FB groups have been posting AI generated stuff I though I'd see what chatgpt thought of SubQmods parameter passing tutorial and I forwarded it your question:
This explanation of passing parameters between C and assembly on the Atari Jaguar using the 68000 makes sense and addresses the core mechanics of stack-based parameter passing. Here's my analysis:

Summary of the Approach:
Passing Parameters from C to Assembly:

The parameters are pushed onto the stack by the C calling convention, and the assembly function accesses these parameters using specific offsets from the stack pointer (A7 or SP).
Each parameter is stored in 32-bit chunks (long words), even if the parameter is a smaller type (like a short or byte), which is common in many C-to-assembly conventions.

Handling Different Parameter Sizes:
The tutorial explains how to handle long integers, short integers, and pointers, correctly accessing the right part of the stack for each.
It provides a good explanation of how to handle different parameter sizes by adjusting the stack offsets.

Saving and Restoring Registers:
The important point about saving and restoring the used registers (with movem.l) is crucial when mixing C and assembly. The caller (C code) expects the registers to remain unchanged after the assembly function returns.

Since saving registers on the stack increases the stack size, it is essential to adjust the offsets for the parameters to account for this.

Handling Return Values:
Although the tutorial doesn’t explicitly cover returning values from the assembly function to the C code, the convention for the Jaguar (like most systems using the 68000) is:

Return values are typically placed in D0 (for integers or pointers) or A0 (for addresses), and the C code expects the return value to be in these registers after the function returns.

Improvements and Thoughts:
Parameter Passing:
The tutorial is solid, but it could explicitly mention that the C compiler generally places the first return value in D0 by convention. If a 32-bit integer or pointer is returned, it should be placed in D0.

For returning complex types (like structures), additional considerations would be needed, possibly passing a pointer to a memory location for the return.

Managing the Stack:
The explanation is clear, but to avoid confusion, it could highlight that A7 is the stack pointer (SP), and many assemblers use SP synonymously with A7. This makes it easier for readers coming from other environments.

Future Consideration:

If the assembly function returns a value, it could be a simple addition like:
=============
move.l d0,-(sp) ; Save D0 to the stack to hold the return value
; Perform some calculations
move.l (sp)+,d0 ; Load the return value from the stack into D0
rts
=============
This would ensure the value gets passed back to the C code as expected.

Register Preservation:
The example correctly handles saving/restoring registers with movem.l, which is good practice. However, it could also mention what registers the calling convention typically allows you to "clobber" (e.g., D0-D1 and A0-A1 are often scratch registers, so no need to save them unless necessary).

Conclusion:
The tutorial is clear, informative, and well-structured for beginners who want to understand the interaction between C and assembly in the Jaguar environment. It correctly handles stack offsets, register preservation, and parameter types.

To answer the specific question:

How to return values from an assembly function? Typically, the return value would be placed in the D0 register before the rts instruction, as that’s the convention used for returning values to C functions. If the value is a pointer, it would go into A0.
What came after the Jaguar was the PS1 which for all it's greatness, ushered in corporate development and with it the bleached, repetitive, bland titles which for the most part we're still playing today. - David Wightman

Post Reply