genINSTR.c Details

 

 

Synopsis

The purpose of this module is to implement various C operations by using CPU specific functions in module gen_user_instr.c. It does this by separating out the complexities of operations between variables of different sizes in this module and only using functions in gen_user_instr.c that are independent and specific to the CPU being used. This module does not rely on the details of the actual CPU being used, but requires specic functions to exist in gen_user_instr.c in order to work.

 

DEBUGGING

This file has the DEBUG_genINSTR which can be enabled or disabled for debuging code.

 

Source Code - Overview

Generic CPU instructions are created by the following functions

 

 

Function called Functions Use by C compiler
genLD_REG gen_ld_reg LD RD, Ra - Load CPU register Rd from register Ra
genLD_IMM gen_ld_imm LD RD, #7 - Load CPU register Rd with an immediate value
genREAD genFP_RD  
  genMEM_RD_IMM  
genMEM_RD_REG gen_mem_rd_reg MEM_RD Rd, @Ra - load cpu register Rd with the memory contents pointed to by register Ra
  genPUSH  
  genPOP  
genMEM_RD_IMM gen_mem_rd_imm MEM_RD Rd, @#addr - load cpu register Rd with memory contents located at address "addr"
genWRITE genFP_WR  
  genMEM_WR_IMM  
genMEM_WR_REG gen_mem_wr_reg MEM_WR Rd, @Ra - Write data from Register Rd to memory (mem_type) pointed to by Register Ra
  genPUSH  
  genPOP  
genMEM_WR_IMM gen_mem_wr_imm MEM_WR Rd, @#addr -Write data from Register Rd to memory (mem_type) pointed to by immediate value following instruction
genFP_RD gen_fp_rd FP_RD Rd, @#stk_offset - write CPU register Rd to stack address = Frame Pointer + offset
genFP_WR gen_fp_wr FP_WR Rd, @#stk_offset - write register Rd contents to stack address = Frame Pointer + offset
genMOD gen_mod Register Rd = Ra % Rb
genDIV gen_div Register Rd = Ra / Rb
genMULT gen_mult Register Rd = Ra * Rb
genSUB gen_sub Register Rd = Ra - Rb
  gen_subc Register Rd = Ra - Rb - carry
genADD gen_add Register Rd = Ra + Rb
  gen_addc Register Rd = Ra - Rb + carry
genASR gen_asr_imm1 Register Rd = Ra >>> Rb (arithmetic shift right0)
  gen_asrc_imm1 Register Rd = {carry_in, Ra} >>> Rb (arithmetic shift right with carry)
genLSR gen_lsr Register Rd = Ra >> Rb (logical shift right)
genLSL gen_lsl Register Rd = Ra << Rb (logical shift left)
genBAND gen_band Register Rd = Ra & Rb (bitwise AND)
genXOR gen_xor Register Rd = Ra ^ Rb
genBOR gen_bor Register Rd = Ra | Rb (bitwise OR)
genLAND gen_land Register Rd = Ra && Rb (logical AND)
  gen_bor Register Rd = Ra | Rb (bitwise OR)
genLOR gen_lor Register Rd = Ra || Rb (logical OR)
  gen_bor Register Rd = Ra | Rb (bitwise OR)
genPUSH gen_push Push a register on the Stack
genPOP gen_pop Pop a register from the Stack
genPUSH_FP gen_push_fp Push the Frame Pointer on the stack
genPOP_FP gen_pop_fp Pop the Frame Pointer from the stack
genHALT   *** not currently used ***
genNOP   *** not currently used ***
genCALL gen_call Call a function
gen_CREATE_STACK_VAR_SPACE gen_create_stack_var_space  
gen_REMOVE_STACK_VAR_SPACE gen_remove_stack_var_space  
genRTN gen_rtn Return from a function
genINC gen_inc Register Rd = Rd + 1
genDEC gen_dec Register Rd = Rd - 1
genCLR gen_clr Register Rd = 0
genNEG gen_neg Register Rd = -Rd
genBNOT gen_bnot Register Rd = ~Rd
genLNOT gen_lnot Register Rd = ! Rd
  gen_bor Register Rd = Ra | Rb
genBR_REG gen_br_reg Branch to address contained in Register Rd
genBR gen_br Take data immediately following the instruction and use as address to branch to
genBNZ gen_bnz Like genBR but only if condition code Z = 0
genBZ gen_bz Like genBR but only if condition code Z = 1
genBPOS gen_bpos Like genBR but only if condition code N = 0
genBNEG gen_bneg Like genBR but only if condition code N = 1
... to be continued    

 

All of the above functions in green call not only those functions shown in brown, but may call other functions too. THe main functions in green are the ones in genINSTR.c. The ones in brown are contained in gen_user_instr.c. gen_user_instr.c contains the lowest level of machine code creation that is specific to the CPU being used.

 

Let's first take a look at a simple function like genCLR to explain code that you will see in more complex functions in genINSTR.c

 

 

Notice that the first parameter passed to genCLR is a structure called gen_data found in kcc.h. This structure contains many items, but we are only using the ' int reg[]' data for the genCLR function.

 

Because C variables may be much larger than a given CPUs internal registers, it may be necessary to use several CPU registers to do operations on a given C variable. For example, let's say the CPU is going to clear a LONG global variable called counter1. Lets say that a LONG is 4 bytes but a CPU register is only 2 bytes. Thus we use 2 CPU registers, maybe R6 and R4 to represent counter1. To clear the variable we could first clear R0 and R1 before doing other operations to the register. When a funtion calls genCLR, notice that it checks to see if num_regs > DEF_REGS to see if more registers are needed than the CPU physically has. If so it generates an error. Its also possible that the code could someday use 'virtual' cpu registers, but for now it's confined to the actual registers. In this example num_regs would be 2 for counter1. Next, a simple loop counter generates actual code by calling gen_clr() twice, once for each physical register. The reg[] array contains a list of which cpu registers are used, so reg[0] = 4 and reg[1] = 6.

 

The other thing to notice is that disassemble_save_inst() uses id_ptr, a pointer returned by functions like genCLR(). This pointer points to an array containg the actual machine code saved by genCLR(). disassemble_save_inst(), takes that data and saves it in an array that will contain all the machine code for the entre C program being compiled. We'll look at the disassemble_save_instr() function, a bit more, later.

 

 

Now consider a varaible called LONG junk. Assume this variable is 8 bytes and a CPU register is 2 bytes. Thus we need 4 physical CPU registers to do an operation on this variable. Assume we have used physical registers R4, R5. Let's say we want to add a value of 10 to junk and thus the compiler decides to call genADD(). Lets say the value of 10 is in R6.

 

 

Notice 3 gen_data structures called Rd, Ra and Rb are passed to this function. Let's assume are addition function was:

 

nextjunk = junk + 10;

 

Thus we are doing a function of the form:

 

Rd = Ra + Rb

 

Rd, Ra and Rb may be of different sizes, in fact lets assume that Rd is 4 bytes (nextjunk), Ra is 8 bytes (junk) and Rb is 2 bytes (10).

In genADD(), ra_num_regs will be 4 and rb_num_regs will be 1. Now we must add these two values of different sizes. They also may be signed values so we need to use both gen_add() and gen_addc() as appropriate.

 

In the FOR loop for the very first add (i == 0), we use a gen_add(). The gen_add(0 function needs to know the physical register numbers to use. Thus for the first add it may be something like:

gen_add(4, 4, 6) where 4 denotes CPU register 4 containg the current junk value and 0 denotes Register 6 containing the value 10.

 

In the next and final loop iteration, the 'else if (i >= Rb_num_regs)' statement will be executed becuase i = 1 and Rb_num_regs = 1. Thus gen_addc() is called as:

gen_addc(5, 5, 6). Notice that Rb->reg[0] will contain a value 0 or -1 as determined by the gen_sign_Rn() function called just before the gen_addc(). In this example, since the value 10 is not a negative value the gen_sign_Rd() puts a 0 in the Register R6 (Rb->reg[0]).

 

 

More to come!!!!!

Where do we go from here?