General > QB64 Discussion
QB64, Assembly, and Software Bloat
fatman2021:
Calling ARM assembly language from C:
--- Code: ---#include <stdio.h>
extern void strcopy(char *d, const char *s);
int main()
{ const char *srcstr = "First string - source ";
char dststr[] = "Second string - destination ";
/* dststr is an array since we’re going to change it */
printf("Before copying:\n");
printf(" %s\n %s\n",srcstr,dststr);
strcopy(dststr,srcstr);
printf("After copying:\n");
printf(" %s\n %s\n",srcstr,dststr);
return (0);
}
--- End code ---
ARM Assembly language string copy subroutine:
--- Code: --- PRESERVE8
AREA SCopy, CODE, READONLY
EXPORT strcopy
strcopy ; R0 points to destination string.
; R1 points to source string.
LDRB R2, [R1],#1 ; Load byte and update address.
STRB R2, [R0],#1 ; Store byte and update address.
CMP R2, #0 ; Check for null terminator.
BNE strcopy ; Keep going if not.
BX lr ; Return.
END
--- End code ---
Follow these steps to build the example from the command line:
Type armasm --debug scopy.s to build the assembly language source.
Type armcc -c --debug strtest.c to build the C source.
Type armlink strtest.o scopy.o -o strtest to link the object files.
Run the image using a compatible debugger with an appropriate debug target.
Calling C from ARM assembly language:
Defining the function in C:
--- Code: ---int g(int a, int b, int c, int d, int e)
{
return a + b + c + d + e;
}
--- End code ---
ARM Assembly language call:
--- Code: --- ; int f(int i) { return g(i, 2*i, 3*i, 4*i, 5*i); }
PRESERVE8
EXPORT f
AREA f, CODE, READONLY
IMPORT g ; i is in R0
STR lr, [sp, #-4]! ; preserve lr
ADD R1, R0, R0 ; compute 2*i (2nd param)
ADD R2, R1, R0 ; compute 3*i (3rd param)
ADD R3, R1, R2 ; compute 5*i
STR R3, [sp, #-4]! ; 5th param on stack
ADD R3, R1, R1 ; compute 4*i (4th param)
BL g ; branch to C function
ADD sp, sp, #4 ; remove 5th param
LDR pc, [sp], #4 ; return
END
--- End code ---
Calling C++ from ARM assembly language:
--- Code: ---struct S { // has no base classes or virtual functions
S(int s) : i(s) { }
int i;
};
extern "C" void cppfunc(S * p) {
// Definition of the C++ function to be called from ASM.
// The body is C++, only the linkage is C.
p->i += 5;
}
--- End code ---
Defining ARM assembly language function:
--- Code: ---AREA Asm, CODE
IMPORT cppfunc ; import the name of the C++
; function to be called from Asm
EXPORT f
f
STMFD sp!,{lr}
MOV R0,#2
STR R0,[sp,#-4]! ; initialize struct
MOV R0,sp ; argument is pointer to struct
BL cppfunc ; call 'cppfunc' so it can change the struct
LDR R0, [sp], #4
ADD R0, R0, R0,LSL #1
LDMFD sp!,{pc}
END
--- End code ---
Calling C++ from C or ARM assembly language
Calling a C++ member function
--- Code: ---struct T {
T(int i) : t(i) { }
int t;
int f(int i);
};
int T::f(int i) { return i + t; }
// Definition of the C++ function to be called from C.
extern "C" int cfunc(T*);
// Declaration of the C function to be called from C++.
int f() {
T t(5); // create an object of type T
return cfunc(&t);
}
--- End code ---
Defining the C function:
--- Code: ---struct T;
extern int _ZN1T1fEi(struct T*, int);
/* the mangled name of the C++ */
/* function to be called */
int cfunc(struct T* t) {
/* Definition of the C function to be called from C++. */
return 3 * _ZN1T1fEi(t, 2); /* like '3 * t->f(2)' */
}
--- End code ---
Implementing the function in ARM assembly language:
--- Code: --- EXPORT cfunc
AREA foo, CODE
IMPORT _ZN1T1fEi
cfunc
STMFD sp!,{lr} ; R0 already contains the object pointer
MOV R1, #2
BL _ZN1T1fEi
ADD R0, R0, R0, LSL #1 ; multiply by 3
LDMFD sp!,{pc}
END
--- End code ---
Implementing the function in embedded ARM assembly:
--- Code: ---struct T {
T(int i) : t(i) { }
int t;
int f(int i);
};
int T::f(int i) { return i + t; }
// Definition of asm function called from C++
__asm int asm_func(T*) {
STMFD sp!, {lr}
MOV R1, #2;
BL __cpp(T::f);
ADD R0, R0, R0, LSL #1 ; multiply by 3
LDMFD sp!, {pc}
}
int f() {
T t(5); // create an object of type T
return asm_func(&t);
}
--- End code ---
fatman2021:
Calling libc using powerpc assembly:
Suppose you want your program to wait for the user to press a key before exiting, you would call the getchar() function which is exported by libc. Calling getchar from our program is rather straight forward, and all we have to do is to include the following line in our program:
--- Code: --- bl .getchar
--- End code ---
Note that in the above line, we have used .getchar instead of getchar. However, including this line alone in our program will not work, and in all probability, this program will just dump core. Do you know why?
We had seen that when we issue the bl instruction, the link register gets overwritten with the address of the instruction following the current one. Hence, after the bl instruction, the link register will contain the address of the following instruction (which is a part of .main). After returning from getchar(), when we issue the instruction blr from .main, we would not return to __start, as we would have over-written the link register set by __start when it issued the bl instruction.
How do we solve the problem? We create a stack frame for main, and save the link register in the frame.
Here is how the program would look like:
--- Code: ---.set r0, 0
.set r1, 1
.set r3, 3
.extern .getchar # Tell the assembler that
# .getchar is an external symbol
.csect
.globl .main
.main:
#### Function prolog begins ####
mflr r0 # Get the link register in r0
stw r0, 8(r1) # Save the link register
stwu r1, -64(r1) # Store the stack pointer, and
# update. Create a frame of 64 bytes.
#### Function prolog ends ####
li r3, 5
bl .getchar
ori r0, r0, 0 # No-op, required by loader after a
# branch to an external function
#### Function epilog begins ####
addi r1, r1, 64 # Restore the stack pointer
lwz r0, 8(r1) # Read the saved link register
mtlr r0
#### Function epilog ends ####
blr
--- End code ---
In the above program, line 5 tells the assembler that .getchar is an external symbol that is not present in the current file.
Load and store operations cannot be performed directly on the link register, and hence we have to copy the contents of the link register to another general purpose register before storing it. The mflr (move from link register) instruction takes as an argument another register, and copies the contents of the link register to the specified register.
In PowerPC, the convention is to use the general purpose register r1 as the stack pointer. In line 12, we save the value of the link register at an offset of 8 bytes from the stack pointer.
In line 13, we use the special instruction stwu (or store word and update) to advance the stack pointer and save the old stack pointer. In this line, stwu stores the value of r1, at the address r1-64, and then stores the value r1-64 in r1. Hence this single instruction allows us to do the two tasks of decrementing the stack pointer, and storing the old stack pointer at one go.
Having done this, we are ready to break into the main logic of the program. We use the bl instruction in line 18 to call getchar.
There are several special instructions, which the assembler treats specially. The instruction in line 19 is treated as a no-op. A no-op is required by loader after a call to an external function is made. We shall see why it is required later. xlc will not compile the program without the no-op. 'as' will not complain about it and compile the application.
Having done our job, we now have to restore the old values of the stack pointer (r1) and the link register. In line 23, we restore the stack pointer to its old value, by adding the immediate value 64 to it. We then load the stored link register value in r0 at line 24. We then use the mtlr (move to link register) to copy the contents of r0 to the link register. We then finish it by calling blr.
When we run this program, we see that it waits for us to enter something, and then returns.
So far, so good, but when happens when I check the exit value returned by this program?
--- Code: ---$ ./a.out
97
--- End code ---
We are no longer getting 5!!!
Just a note of caution, in this post, I have not followed the stack-linkage convention in its entirety, and have tried to simplify things and have only tried to capture the essence of stack-linkage. I will probably return to this topic in a later post.
Navigation
[0] Message Index
[*] Previous page
Go to full version