lcc has a version of stdio.h. Included is code for, printf.asm scanf.asm stdio.h getchar.asm putchar.asm stdio.asm Use these in the usual way in C code.
The C-to-LC3 compiler, lcc, produces LC3 assembly code from C source code. Specifically, it produces code compatible with the lc3as assembler. There is no stand-alone linker available; consequently, if we wish to mix assembly code and C code, we must do the linking by hand. To do that, we must first understand the layout and meaning of the assembly code lcc produces. Note: C code can be separately compiled and linked in the usual way using lcc.
SUMMARY:
We start with the simplest example code we can think of.
Here is a C source file, myC.c:
int main(void) {
return(0);
}
The lcc compiler produces LC3 assembly (see below). Here
is the result in shorthand. NB-- "=" means assign a value;
"<==" means points to; "==" means has same value.
;;;-------------------------------
PREAMBLE:
;;;--- set up stack pointers (push decrements SP)
STACKBOTTOM <== SP <== BP
;;;--- set up global data pointer
GlobalDataArea <== GDP
;;;--- Call main, on return
;;;--- jump to OS.
R7 <== (*GDP).entryPoint
JSRR R7
HALT
ENTER:
;;;--- sets stack call frame. Result stack
;;;--- looks like this, (big address at top):
STACKBOTTOM
frame1.retVal
frame1.retAddr == address to use to when exiting.
frame1.oldBP == prior BP value
frame1.end <== SP <== BP
BODY:
frame1.retVal = (*GDP).const
EXIT:
;;;--- resets stack call frame. Result stack
;;;--- looks like this, (big address at top):
STACKBOTTOM <== BP
frame1.retVal = (*GDP).const <== SP
frame1.retAddr == R7
RET
;;;--------------------------------------
DETAILS:
Here is the C source file again, myC.c:
int main(void) {
return(0);
}
Here is the result of running "lcc f.c", which gave
us "a.asm". (Usually, we need -s flag to get .asm)
My added comments are preceded by ";----".
======(a.asm starts here) ============================
;------- PREAMBLE-init-user-SP-BP ---------------
;---- Sets up the user's initial stack, SP and BP.
;---- lcc assumes this is a user program.
;---- Sets SP and BP to large end of memory.
;---- Recall, xF000-xFFFF are I/O registers.
;---- So, this sets SP == BP == xEFFF.
;---- BP is R5.
.Orig x3000
INIT_CODE
LEA SP, #-1 ;---- SP == PC-1 == x3000
ADD BP, SP, #0 ;---- BP == SP == x3000
ADD SP, SP, SP ;---- SP == 2*SP == x6000
ADD SP, SP, SP ;---- SP == 2*SP == xC000
ADD SP, SP, BP ;---- SP == SP+BP == xF000
ADD SP, SP, #-1 ;---- SP == SP-- == xEFFF
ADD BP, BP, BP ;---- [ignored]
ADD BP, SP, #0 ;---- BP == SP == xEFFF
;--------- PREAMBLE-init-main ---------------------
;---- Inits a register, R4, aka GDP, with a pointer
;---- we can then use to access our data area
;---- from anywhere in memory. Also moves
;---- a pointer into R7, to jump to main(), which
;---- allows for an arbitrary jump.
;---- These pointers were created by lcc's linker.
LD GDP, GLOBAL_DATA_POINTER ;---- GDP == (address of data area).
LD R7, GLOBAL_MAIN_POINTER ;---- R7 == (address of main()).
jsrr R7 ;---- Jump to main().
HALT ;---- When main() exits, jump to OS
;---- via the Trap Vector Table.
GLOBAL_DATA_POINTER .FILL GLOBAL_DATA_START
GLOBAL_MAIN_POINTER .FILL main
;;;;;;;;;;;;;;;;;;;;;;;;;;;;main;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;---------- We jumped here from PREAMBLE-init-main.
;---------- ENTER (standard code to enter a procedure.)
main ;----
;---- SP========> frame0.end (at addr xEFFF)
ADD SP, SP, #-2 ;---- SP-- =====> frame1.retVal (at addr xEFFE)
STR R7, SP, #0 ;---- PUSH R7 == frame1.retAddr (at addr xEFFD)
ADD SP, SP, #-1 ;---- PUSH BP == frame1.oldBP (at addr xEFFC)
STR BP, SP, #0 ;----
ADD BP, SP, #-1 ;---- BP =======> frame1.end (at addr xEFFB)
ADD SP, SP, #-1 ;---- SP == BP
;---------- BODY (the instructions in the body of the procedure.)
;---------- In this case, nothing to do but prepare the return value
;---------- by fetching a constant value from the global data area.
ADD R7, GDP, #1 ;---- R7 == &GD.L2_myC
LDR R7, R7, #0 ;---- R7 == GD.L2_myC == 0
;---------- LEAVE (standard code to exit a procedure.)
;---------- Puts return value in frame, resets BP as it was before,
;---------- and resets SP to point to return value in frame,
;---------- ready to be popped by code that called this procedure.
lc3_L1_myC ;----
STR R7, BP, #3 ;---- BP+3 =======> frame1.retval == R7 (at xEFFE)
ADD SP, BP, #1 ;---- SP == BP+1 => frame1.oldBP (at xEFFC)
LDR BP, SP, #0 ;---- POP BP =====> frame0.end (at xEFFF)
ADD SP, SP, #1 ;---- SP =========> frame1.retAddr (at xEFFD)
LDR R7, SP, #0 ;---- POP R7 == frame1.retAddr (addr in xEFFD)
ADD SP, SP, #1 ;---- SP =========> frame1.retVal (at xEFFE)
RET ;---- return via R7
;---------- GLOBAL DATA, STATIC, CONSTANTS AREA.
;---------- Pointers to jump locations, and data items.
;---------- AKA "GD" in the above.
GLOBAL_DATA_START ;----
L1_myC .FILL lc3_L1_myC ;---- GD.LEAVE_location
L2_myC .FILL #0 ;---- GD.constant
.END ;----