{ $Id$ Copyright (c) 1998-2002 by Florian Klaempfl This unit implements the x86 specific class for the register allocator This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. **************************************************************************** } unit rgx86; {$i fpcdefs.inc} interface uses cpubase, cpuinfo, aasmbase,aasmtai,aasmcpu, cclasses,globtype,cgbase,rgobj; type trgx86 = class(trgobj) function instr_spill_register(list:Taasmoutput; instr:taicpu; const r:Tsuperregisterset; const spilltemplist:Tspill_temp_list): boolean;override; end; tpushedsavedloc = record case byte of 0: (pushed: boolean); 1: (ofs: longint); end; tpushedsavedfpu = array[tsuperregister] of tpushedsavedloc; trgx86fpu = class { The "usableregsxxx" contain all registers of type "xxx" that } { aren't currently allocated to a regvar. The "unusedregsxxx" } { contain all registers of type "xxx" that aren't currently } { allocated } unusedregsfpu,usableregsfpu : Tsuperregisterset; { these counters contain the number of elements in the } { unusedregsxxx/usableregsxxx sets } countunusedregsfpu : byte; { Contains the registers which are really used by the proc itself. It doesn't take care of registers used by called procedures } used_in_proc : tcpuregisterset; {reg_pushes_other : regvarother_longintarray; is_reg_var_other : regvarother_booleanarray; regvar_loaded_other : regvarother_booleanarray;} { tries to hold the amount of times which the current tree is processed } t_times: longint; fpuvaroffset : byte; constructor create; function getregisterfpu(list: taasmoutput) : tregister; procedure ungetregisterfpu(list: taasmoutput; r : tregister); { pushes and restores registers } procedure saveusedfpuregisters(list:Taasmoutput; var saved:Tpushedsavedfpu; const s:Tcpuregisterset); procedure restoreusedfpuregisters(list:Taasmoutput; const saved:Tpushedsavedfpu); { corrects the fpu stack register by ofs } function correct_fpuregister(r : tregister;ofs : byte) : tregister; end; implementation uses systems, verbose; const { This value is used in tsaved. If the array value is equal to this, then this means that this register is not used.} reg_not_saved = $7fffffff; {****************************************************************************** Trgcpu ******************************************************************************} function trgx86.instr_spill_register(list:Taasmoutput; instr:taicpu; const r:Tsuperregisterset; const spilltemplist:Tspill_temp_list): boolean; { Spill the registers in r in this instruction. Returns true if any help registers are used. This procedure has become one big hack party, because of the huge amount of situations you can have. The irregularity of the i386 instruction set doesn't help either. (DM) } var i:byte; supreg:Tsuperregister; subreg:Tsubregister; helpreg:Tregister; helpins:Taicpu; op:Tasmop; hopsize:Topsize; pos:Tai; begin {Situation examples are in intel notation, so operand order: mov eax , ebx ^^^ ^^^ oper[1] oper[0] (DM)} result:=false; with taicpu(instr) do begin case ops of 0: ; 1: begin if (oper[0]^.typ=top_reg) and (getregtype(oper[0]^.reg)=regtype) then begin supreg:=getsupreg(oper[0]^.reg); if supregset_in(r,supreg) then begin {Situation example: push r20d ; r20d must be spilled into [ebp-12] Change into: push [ebp-12] ; Replace register by reference } { hopsize:=reg2opsize(oper[0].reg);} oper[0]^.typ:=top_ref; new(oper[0]^.ref); oper[0]^.ref^:=spilltemplist[supreg]; { oper[0]^.ref^.size:=hopsize;} end; end; if oper[0]^.typ=top_ref then begin supreg:=getsupreg(oper[0]^.ref^.base); if supregset_in(r,supreg) then begin {Situation example: push [r21d+4*r22d] ; r21d must be spilled into [ebp-12] Change into: mov r23d,[ebp-12] ; Use a help register push [r23d+4*r22d] ; Replace register by helpregister } subreg:=getsubreg(oper[0]^.ref^.base); if oper[0]^.ref^.index=NR_NO then pos:=Tai(previous) else pos:=get_insert_pos(Tai(previous),getsupreg(oper[0]^.ref^.index),RS_INVALID,RS_INVALID); getregisterinline(list,pos,subreg,helpreg); result:=true; helpins:=Taicpu.op_ref_reg(A_MOV,reg2opsize(oper[0]^.ref^.base),spilltemplist[supreg],helpreg); if pos=nil then list.insertafter(helpins,list.first) else list.insertafter(helpins,pos.next); ungetregisterinline(list,helpins,helpreg); forward_allocation(Tai(helpins.next),instr); oper[0]^.ref^.base:=helpreg; end; supreg:=getsupreg(oper[0]^.ref^.index); if supregset_in(r,supreg) then begin {Situation example: push [r21d+4*r22d] ; r22d must be spilled into [ebp-12] Change into: mov r23d,[ebp-12] ; Use a help register push [r21d+4*r23d] ; Replace register by helpregister } subreg:=getsubreg(oper[0]^.ref^.index); if oper[0]^.ref^.base=NR_NO then pos:=Tai(instr.previous) else pos:=get_insert_pos(Tai(instr.previous),getsupreg(oper[0]^.ref^.base),RS_INVALID,RS_INVALID); getregisterinline(list,pos,subreg,helpreg); result:=true; helpins:=Taicpu.op_ref_reg(A_MOV,reg2opsize(oper[0]^.ref^.index),spilltemplist[supreg],helpreg); if pos=nil then list.insertafter(helpins,list.first) else list.insertafter(helpins,pos.next); ungetregisterinline(list,helpins,helpreg); forward_allocation(Tai(helpins.next),instr); oper[0]^.ref^.index:=helpreg; end; end; end; 2, 3 : begin { Opcodes with 3 registers are shrd/shld, where the 3rd operand is const or CL, that doesn't need spilling } { First spill the registers from the references. This is required because the reference can be moved from this instruction to a MOV instruction when spilling of the register operand is done } for i:=0 to 1 do if oper[i]^.typ=top_ref then begin supreg:=getsupreg(oper[i]^.ref^.base); if supregset_in(r,supreg) then begin {Situation example: add r20d,[r21d+4*r22d] ; r21d must be spilled into [ebp-12] Change into: mov r23d,[ebp-12] ; Use a help register add r20d,[r23d+4*r22d] ; Replace register by helpregister } subreg:=getsubreg(oper[i]^.ref^.base); if i=1 then pos:=get_insert_pos(Tai(instr.previous),getsupreg(oper[i]^.ref^.index),getsupreg(oper[0]^.reg),RS_INVALID) else pos:=get_insert_pos(Tai(instr.previous),getsupreg(oper[i]^.ref^.index),RS_INVALID,RS_INVALID); getregisterinline(list,pos,subreg,helpreg); result:=true; helpins:=Taicpu.op_ref_reg(A_MOV,reg2opsize(oper[i]^.ref^.base),spilltemplist[supreg],helpreg); if pos=nil then list.insertafter(helpins,list.first) else list.insertafter(helpins,pos.next); oper[i]^.ref^.base:=helpreg; ungetregisterinline(list,helpins,helpreg); forward_allocation(Tai(helpins.next),instr); end; supreg:=getsupreg(oper[i]^.ref^.index); if supregset_in(r,supreg) then begin {Situation example: add r20d,[r21d+4*r22d] ; r22d must be spilled into [ebp-12] Change into: mov r23d,[ebp-12] ; Use a help register add r20d,[r21d+4*r23d] ; Replace register by helpregister } subreg:=getsubreg(oper[i]^.ref^.index); if i=1 then pos:=get_insert_pos(Tai(instr.previous),getsupreg(oper[i]^.ref^.base), getsupreg(oper[0]^.reg),RS_INVALID) else pos:=get_insert_pos(Tai(instr.previous),getsupreg(oper[i]^.ref^.base),RS_INVALID,RS_INVALID); getregisterinline(list,pos,subreg,helpreg); result:=true; helpins:=Taicpu.op_ref_reg(A_MOV,reg2opsize(oper[i]^.ref^.index),spilltemplist[supreg],helpreg); if pos=nil then list.insertafter(helpins,list.first) else list.insertafter(helpins,pos.next); oper[i]^.ref^.index:=helpreg; ungetregisterinline(list,helpins,helpreg); forward_allocation(Tai(helpins.next),instr); end; end; if (oper[0]^.typ=top_reg) and (getregtype(oper[0]^.reg)=regtype) then begin supreg:=getsupreg(oper[0]^.reg); subreg:=getsubreg(oper[0]^.reg); if supregset_in(r,supreg) then if oper[1]^.typ=top_ref then begin {Situation example: add [r20d],r21d ; r21d must be spilled into [ebp-12] Change into: mov r22d,[ebp-12] ; Use a help register add [r20d],r22d ; Replace register by helpregister } pos:=get_insert_pos(Tai(instr.previous),getsupreg(oper[0]^.reg), getsupreg(oper[1]^.ref^.base),getsupreg(oper[1]^.ref^.index)); getregisterinline(list,pos,subreg,helpreg); result:=true; helpins:=Taicpu.op_ref_reg(A_MOV,reg2opsize(oper[0]^.reg),spilltemplist[supreg],helpreg); if pos=nil then list.insertafter(helpins,list.first) else list.insertafter(helpins,pos.next); oper[0]^.reg:=helpreg; ungetregisterinline(list,helpins,helpreg); forward_allocation(Tai(helpins.next),instr); end else begin {Situation example: add r20d,r21d ; r21d must be spilled into [ebp-12] Change into: add r20d,[ebp-12] ; Replace register by reference } oper[0]^.typ:=top_ref; new(oper[0]^.ref); oper[0]^.ref^:=spilltemplist[supreg]; end; end; if (oper[1]^.typ=top_reg) and (getregtype(oper[1]^.reg)=regtype) then begin supreg:=getsupreg(oper[1]^.reg); subreg:=getsubreg(oper[1]^.reg); if supregset_in(r,supreg) then begin if oper[0]^.typ=top_ref then begin {Situation example: add r20d,[r21d] ; r20d must be spilled into [ebp-12] Change into: mov r22d,[r21d] ; Use a help register add [ebp-12],r22d ; Replace register by helpregister } pos:=get_insert_pos(Tai(instr.previous),getsupreg(oper[0]^.ref^.base), getsupreg(oper[0]^.ref^.index),RS_INVALID); getregisterinline(list,pos,subreg,helpreg); result:=true; op:=A_MOV; hopsize:=opsize; {Save old value...} if (opcode=A_MOVZX) or (opcode=A_MOVSX) or (opcode=A_LEA) then begin {Because 'movzx memory,register' does not exist...} op:=opcode; opcode:=A_MOV; opsize:=reg2opsize(oper[1]^.reg); end; helpins:=Taicpu.op_ref_reg(op,hopsize,oper[0]^.ref^,helpreg); if pos=nil then list.insertafter(helpins,list.first) else list.insertafter(helpins,pos.next); dispose(oper[0]^.ref); oper[0]^.typ:=top_reg; oper[0]^.reg:=helpreg; oper[1]^.typ:=top_ref; new(oper[1]^.ref); oper[1]^.ref^:=spilltemplist[supreg]; ungetregisterinline(list,helpins,helpreg); forward_allocation(Tai(helpins.next),instr); end else begin {Situation example: add r20d,r21d ; r20d must be spilled into [ebp-12] Change into: add [ebp-12],r21d ; Replace register by reference } if (opcode=A_MOVZX) or (opcode=A_MOVSX) then begin {Because 'movzx memory,register' does not exist...} result:=true; op:=opcode; hopsize:=opsize; opcode:=A_MOV; opsize:=reg2opsize(oper[1]^.reg); pos:=get_insert_pos(Tai(instr.previous),getsupreg(oper[0]^.reg),RS_INVALID,RS_INVALID); getregisterinline(list,pos,subreg,helpreg); helpins:=Taicpu.op_reg_reg(op,hopsize,oper[0]^.reg,helpreg); if pos=nil then list.insertafter(helpins,list.first) else list.insertafter(helpins,pos.next); oper[0]^.reg:=helpreg; ungetregisterinline(list,helpins,helpreg); forward_allocation(Tai(helpins.next),instr); end; oper[1]^.typ:=top_ref; new(oper[1]^.ref); oper[1]^.ref^:=spilltemplist[supreg]; end; end; end; { The i386 instruction set never gets boring... some opcodes do not support a memory location as destination } if (oper[1]^.typ=top_ref) and ( (oper[0]^.typ=top_const) or ((oper[0]^.typ=top_reg) and (getregtype(oper[0]^.reg)=regtype)) ) then begin case opcode of A_SHLD,A_SHRD, A_IMUL : begin {Yikes! We just changed the destination register into a memory location above here. Situation examples: imul [ebp-12],r21d ; We need a help register imul [ebp-12], ; We need a help register Change into: mov r22d,[ebp-12] ; Use a help instruction (only for IMUL) imul r22d,r21d ; Replace reference by helpregister mov [ebp-12],r22d ; Use another help instruction} getregisterinline(list,Tai(previous),subreg,helpreg); result:=true; {First help instruction.} helpins:=Taicpu.op_ref_reg(A_MOV,opsize,oper[1]^.ref^,helpreg); if previous=nil then list.insert(helpins) else list.insertafter(helpins,previous); {Second help instruction.} helpins:=Taicpu.op_reg_ref(A_MOV,opsize,helpreg,oper[1]^.ref^); dispose(oper[1]^.ref); oper[1]^.typ:=top_reg; oper[1]^.reg:=helpreg; list.insertafter(helpins,instr); ungetregisterinline(list,instr,helpreg); end; end; end; { The i386 instruction set never gets boring... some opcodes do not support a memory location as source } if (oper[0]^.typ=top_ref) and (oper[1]^.typ=top_reg) and (getregtype(oper[1]^.reg)=regtype) then begin case opcode of A_BT,A_BTS, A_BTC,A_BTR : begin {Yikes! We just changed the source register into a memory location above here. Situation example: bt r21d,[ebp-12] ; We need a help register Change into: mov r22d,[ebp-12] ; Use a help instruction (only for IMUL) bt r21d,r22d ; Replace reference by helpregister} getregisterinline(list,Tai(previous),subreg,helpreg); result:=true; {First help instruction.} helpins:=Taicpu.op_ref_reg(A_MOV,opsize,oper[0]^.ref^,helpreg); if previous=nil then list.insert(helpins) else list.insertafter(helpins,previous); dispose(oper[0]^.ref); oper[0]^.typ:=top_reg; oper[0]^.reg:=helpreg; ungetregisterinline(list,helpins,helpreg); end; end; end; end; else internalerror(200409202); end; end; end; {****************************************************************************** Trgx86fpu ******************************************************************************} constructor Trgx86fpu.create; var i:Tsuperregister; begin used_in_proc:=[]; t_times := 0; unusedregsfpu:=usableregsfpu; end; function trgx86fpu.getregisterfpu(list: taasmoutput) : tregister; begin { note: don't return R_ST0, see comments above implementation of } { a_loadfpu_* methods in cgcpu (JM) } result:=NR_ST; end; procedure trgx86fpu.ungetregisterfpu(list : taasmoutput; r : tregister); begin { nothing to do, fpu stack management is handled by the load/ } { store operations in cgcpu (JM) } end; function trgx86fpu.correct_fpuregister(r : tregister;ofs : byte) : tregister; begin correct_fpuregister:=r; setsupreg(correct_fpuregister,ofs); end; procedure trgx86fpu.saveusedfpuregisters(list: taasmoutput; var saved : tpushedsavedfpu; const s: tcpuregisterset); var r : tregister; hr : treference; begin used_in_proc:=used_in_proc+s; {$warning TODO firstsavefpureg} (* { don't try to save the fpu registers if not desired (e.g. for } { the 80x86) } if firstsavefpureg <> R_NO then for r.enum:=firstsavefpureg to lastsavefpureg do begin saved[r.enum].ofs:=reg_not_saved; { if the register is used by the calling subroutine and if } { it's not a regvar (those are handled separately) } if not is_reg_var_other[r.enum] and (r.enum in s) and { and is present in use } not(r.enum in unusedregsfpu) then begin { then save it } tg.GetTemp(list,extended_size,tt_persistent,hr); saved[r.enum].ofs:=hr.offset; cg.a_loadfpu_reg_ref(list,OS_FLOAT,r,hr); cg.a_reg_dealloc(list,r); include(unusedregsfpu,r.enum); inc(countunusedregsfpu); end; end; *) end; procedure trgx86fpu.restoreusedfpuregisters(list : taasmoutput; const saved : tpushedsavedfpu); var r,r2 : tregister; hr : treference; begin {$warning TODO firstsavefpureg} (* if firstsavefpureg <> R_NO then for r.enum:=lastsavefpureg downto firstsavefpureg do begin if saved[r.enum].ofs <> reg_not_saved then begin r2.enum:=R_INTREGISTER; r2.number:=NR_FRAME_POINTER_REG; reference_reset_base(hr,r2,saved[r.enum].ofs); cg.a_reg_alloc(list,r); cg.a_loadfpu_ref_reg(list,OS_FLOAT,hr,r); if not (r.enum in unusedregsfpu) then { internalerror(10) in n386cal we always save/restore the reg *state* using save/restoreunusedstate -> the current state may not be real (JM) } else begin dec(countunusedregsfpu); exclude(unusedregsfpu,r.enum); end; tg.UnGetTemp(list,hr); end; end; *) end; (* procedure Trgx86fpu.saveotherregvars(list: taasmoutput; const s: totherregisterset); var r: Tregister; begin if not(cs_regvars in aktglobalswitches) then exit; if firstsavefpureg <> NR_NO then for r.enum := firstsavefpureg to lastsavefpureg do if is_reg_var_other[r.enum] and (r.enum in s) then store_regvar(list,r); end; *) end. { $Log$ Revision 1.6 2004-09-27 14:49:45 peter * handle 3 operand opcodes the same as 2 operand opcodes, the third operand can only be a const or register CL, so it doesn't affect spilling * support shrd/shld that don't allow memory operands Revision 1.5 2004/09/26 07:15:07 florian * ie checking in spilling code improved Revision 1.4 2004/06/20 08:55:32 florian * logs truncated Revision 1.3 2004/06/16 20:07:11 florian * dwarf branch merged Revision 1.2.2.1 2004/04/10 12:36:42 peter * fixed alignment issues Revision 1.2 2004/01/12 16:37:59 peter * moved spilling code from taicpu to rg }