| Mem (Bin ((Plus|Uplus), Const n, e)) | Mem (Bin ((Plus|Uplus), e, Const n)) when seize_bits n -> let d = new_temp () and s = emit_exp e in emit_lw d n s ; d | Mem (Bin (Minus, e, Const n)) when seize_bits (-n) -> let d = new_temp () and s = emit_exp e in emit_lw d (-n) s ; d | Mem e -> let d = new_temp () and s = emit_exp e in emit_lw d 0 s ; d |
let emit_bcc op s0 s1 lab1 lab2 = emit (Oper (branch_of_relop op^" ^s0, ^s1, "^Gen.label_string lab1, [s0 ; s1], [], Some [lab1 ; lab2])) let emit_stm frame = function … | Cjump (op, e1, e2, lab1, lab2) -> let s1 = emit_exp e1 and s2 = emit_exp e2 in emit_bcc op s1 s2 lab1 lab2 … |
let emit_sw s0 i s1 = emit (Oper ("sw ^s0,"^string_of_int i^"(^s1)", [s0 ; s1], [], None)) let emit_stm frame = function … | Move_mem ((Bin ((Plus|Uplus), Const n, ea)|Bin ((Plus|Uplus), ea, Const n)), e) when seize_bits n -> let s0 = emit_exp ea and s1 = emit_exp e in emit_sw s1 n s0 | Move_mem (Bin (Minus, ea, Const n), e) when seize_bits (-n) -> let s0 = emit_exp ea and s1 = emit_exp e in emit_sw s1 (-n) s0 | Move_mem(ea, e) -> let s0 = emit_exp ea and s1 = emit_exp e in emit_sw s1 0 s0 … |
Move_mem
) sont les mêmes que dans le cas de la lecture en mémoire
(constructeur Mem
des expressions).emit_jal
, chargée
d'émettre l'instruction « jal ».
Cette fonction prend un frame en argument.
let rec nfirsts n rs = if n = 0 then [] else match rs with | [] -> [] | r::rs -> r::nfirsts (n-1) rs let emit_jal f = let srcs = nfirsts (List.length (Frame.frame_args f)) arg_registers and dests = trash f in emit (Oper ("jal "^Gen.label_string (Frame.frame_name f), dests, srcs, None)) |
trash
). Pour préciser, les destinations
sont le registre ra dont l'instruction jal détruit
certainement le contenu, plus tous les registres dont la fonction
appelée a conventionellement le droit de détruire le contenu (c'est à
dire call_trash
en général).
let rec emit_args regs args = match regs, args with | r::rs, e::es -> let s = emit_exp e in emit_move r s ; emit_args rs es | _, [] -> () | [], _ -> assert false (* Par hypothèse *) |
let emit_call caller_frame callee_frame args = emit_args arg_registers args ; emit_jal callee_frame ; v0 |
emit_call
et plus
spécifiquement emit_args
réalisés à la question précédente.
Voici un code possible :
let emit_store_sp_offset i s0 = let offset = string_of_int i in emit (Oper ("sw ^s0, "^offset^"($sp)", [s0], [], None)) let rec emit_args regs args = match regs, args with ... (* Comme avant *) | [], _ -> (* Plus de registres, encore des arguments effectifs *) let rec to_stack i = function | [] -> () | e :: es -> let s = emit_exp e in emit_store_sp_offset (Frame.wordsize * i) s ; to_stack (i+1) es in to_stack 0 args |
0($sp)
», le sixième juste en deça
(adresse « 4($sp)
»), etc.
Il faut également informer l'appelant qu'il doit reserver les
mots correspondant dans le sommet de son frame.
let emit_call caller_frame callee_frame args = emit_args arg_registers args ; let nargs_instack = let nregs = List.length arg_registers and nargs = List.length args in if nargs <= nregs then 0 else nargs-nregs in Frame.make_space_for_args caller_frame nargs_instack ; emit_jal callee_frame ; v0 |
let emit_prolog f decr_sp = (* Émission du point d'entrée *) emit_label (Frame.frame_name f) ; (* Allouer le frame en décrémentant sp *) emit (Oper (decr_sp, [], [], None)) ; (* Sauvegarder les callee_save *) let saved_callee_save = Gen.new_temp ():: List.map (fun _ -> Gen.new_temp ()) callee_save_registers in emit_moves saved_callee_save (ra::callee_save_registers) ; (* Copier les arguments des registres conventionnels vers les temporaires idoines *) let rec get_args temps regs = match temps, regs with | t::ts, r::rs -> emit_move t r ; get_args ts rs | [],_ -> () | _,[] -> failwith (Printf.sprintf "Plus de %i arguments" (List.length arg_registers)) in get_args (Frame.frame_args f) arg_registers ; (* Finalement renvoyer les sauvegardes des callee-saves *) saved_callee_save |
get_args
et plus précisément que l'on doit remplacer l'echec
final par la lecture de la pile.
Mais attention, le pointeur de pile a dejà été diminué !.
C'est à dire que le (disons) cinquième argument est à l'adresse
F+0($sp)
, le sixième à l'adresse F+0($sp)
etc.
où F
est la taille du frame de la fonction f
dont nous sommes en
train d'émettre le prologue.
La constante F
est pour le moment inconnue, mais nous savons
qu'elle sera connue de l'assembleur sous le nom
Frame.frame_size_label
f
.
Bref on pourra se débrouiller (mais ici la glace est mince) comme
ceci :
let emit_load_fp_offset frame_size i d0 = let sp_offset = (string_of_int i) ^ "+" ^ frame_size in emit (Oper ("lw ^d0, "^sp_offset ^"($sp)", [], [d0], None)) let emit_prolog f decr_sp = ... let rec get_args temps regs = match temps, regs with | t::ts, r::rs -> emit_move t r ; get_args ts rs | [],_ -> () | _,[] -> let frame_size = Frame.frame_size_label f in let rec from_stack i = function | [] -> () | arg :: args -> emit_load_fp_offset frame_size (Frame.wordsize * i) arg ; from_stack (i+1) args in from_stack 0 temps in ... |
tri
prenant trois arguments.
Nous compilons en mode « -3 » où un seul registre (a0) est
affecté au passage des arguments.
Voici un appel de la fonction tri
lw $a0, 0($gp) sw $zero, 0($sp) lw $156, 4($gp) sw $156, 4($sp) jal tri |
tri
# Point d'entrée tri: # Allocation du frame subu $sp, $sp, tri_f # Sauvegarde des callee-saves move $125, $ra # Copie des paramètres effectifs dans les temporaires idoines move $106, $a0 lw $107, 0+tri_f($sp) lw $108, 4+tri_f($sp) |
tri_f
, par exemple (option
« -spill »)
tri_f=136 |
0+tri_f
et 4+tri_f
de sorte que ce seront
les entiers 136 et 140 qui se retrouvent dans le code machine
final.emit_stm
passe un registre (plus
exactement une option de registre) à la fonction emit_exp
.
Si la fonction emit_exp
a besoin d'un registre pour rendre le
résultat de sa compilation, alors elle utlisera plutôt le registre
qu'on lui a donné.
Cela donne par exemple :
let rec emit_stm = function ... | Move_temp (d, e) -> let s = emit_exp (Some d) e in if s <> d then emit_move d s |
lw r1,i(r2)
en lecture, sw r1,i(r2)
en
écriture).
La fonction emit_addr
traite l'émission des adresses et est
apellée à la fois par emit_exp
et emit_stm
.
Il en result un codage plus compact, mais qui obscursit un peu le
principe de couverture par des tuiles.mult_by_bits
).
Dans la pratique, au plus un décalage est probablement suffisant.
(* Les bits a` un d'un nombre positif, plus significatifs en premier *) let bits = let rec bits_rec r k = function | 0 -> r | i -> if i mod 2 = 0 then bits_rec r (k+1) (i/2) else bits_rec (k::r) (k+1) (i/2) in bits_rec [] 0 (* Multiplication par un nombre constant *) let mult_by_bits d s = function | 0 -> zero | 1 -> s | i -> let iabs = if i < 0 then -i else i in let ibits = bits iabs in if List.length ibits >= 5 then emit_const_arg Times d s i else begin let t = if d = s then Gen.new_temp () else d in let rec sll_rec = function | k1::k2::rest -> emit_add t t s ; emit_sll t t (k1-k2) ; sll_rec (k2::rest) | [0] -> emit_add t t s | [k] -> emit_add t t s ; emit_sll t t k | [] -> assert false in begin match ibits with | k1::k2::rest -> emit_sll t s (k1-k2) ; sll_rec (k2::rest) ; | [k] -> emit_sll t s k ; | [] -> assert false end ; if i > 0 then t else begin emit_neg d t ; d end end |
Ce document a été traduit de LATEX par HEVEA.