%% -*- erlang-indent-level: 2 -*- %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. -ifdef(HIPE_AMD64). -define(HIPE_X86_RA_POSTCONDITIONS, hipe_amd64_ra_postconditions). -define(HIPE_X86_REGISTERS, hipe_amd64_registers). -define(HIPE_X86_SPECIFIC, hipe_amd64_specific). -define(ECX, rcx). -else. -define(HIPE_X86_RA_POSTCONDITIONS, hipe_x86_ra_postconditions). -define(HIPE_X86_REGISTERS, hipe_x86_registers). -define(HIPE_X86_SPECIFIC, hipe_x86_specific). -define(ECX, ecx). -endif. -module(?HIPE_X86_RA_POSTCONDITIONS). -export([check_and_rewrite/3]). -include("../x86/hipe_x86.hrl"). -define(HIPE_INSTRUMENT_COMPILER, true). -include("../main/hipe.hrl"). -define(count_temp(T), ?cons_counter(counter_mfa_mem_temps, T)). check_and_rewrite(CFG, Coloring, Strategy) -> %% io:format("Converting\n"), TempMap = hipe_temp_map:cols2tuple(Coloring, ?HIPE_X86_SPECIFIC, no_context), %% io:format("Rewriting\n"), do_bbs(hipe_x86_cfg:labels(CFG), TempMap, Strategy, CFG, false). do_bbs([], _, _, CFG, DidSpill) -> {CFG, DidSpill}; do_bbs([Lbl|Lbls], TempMap, Strategy, CFG0, DidSpill0) -> Code0 = hipe_bb:code(BB = hipe_x86_cfg:bb(CFG0, Lbl)), {Code, DidSpill} = do_insns(Code0, TempMap, Strategy, [], DidSpill0), CFG = hipe_x86_cfg:bb_add(CFG0, Lbl, hipe_bb:code_update(BB, Code)), do_bbs(Lbls, TempMap, Strategy, CFG, DidSpill). do_insns([I|Insns], TempMap, Strategy, Accum, DidSpill0) -> {NewIs, DidSpill1} = do_insn(I, TempMap, Strategy), do_insns(Insns, TempMap, Strategy, lists:reverse(NewIs, Accum), DidSpill0 or DidSpill1); do_insns([], _TempMap, _Strategy, Accum, DidSpill) -> {lists:reverse(Accum), DidSpill}. do_insn(I, TempMap, Strategy) -> % Insn -> {Insn list, DidSpill} case I of #alu{} -> do_alu(I, TempMap, Strategy); #cmp{} -> do_cmp(I, TempMap, Strategy); #imul{} -> do_imul(I, TempMap, Strategy); #jmp_switch{} -> do_jmp_switch(I, TempMap, Strategy); #lea{} -> do_lea(I, TempMap, Strategy); #move{} -> do_move(I, TempMap, Strategy); #move64{} -> do_move64(I, TempMap, Strategy); #movsx{} -> do_movx(I, TempMap, Strategy); #movzx{} -> do_movx(I, TempMap, Strategy); #fmove{} -> do_fmove(I, TempMap, Strategy); #pseudo_spill_move{} -> do_pseudo_spill_move(I, TempMap, Strategy); #shift{} -> do_shift(I, TempMap, Strategy); #test{} -> do_test(I, TempMap, Strategy); _ -> %% comment, jmp*, label, pseudo_call, pseudo_jcc, pseudo_tailcall, %% pseudo_tailcall_prepare, push, ret {[I], false} end. %%% Fix an alu op. do_alu(I, TempMap, Strategy) -> #alu{src=Src0,dst=Dst0} = I, {FixSrc,Src,FixDst,Dst,DidSpill} = do_binary(Src0, Dst0, TempMap, Strategy), {FixSrc ++ FixDst ++ [I#alu{src=Src,dst=Dst}], DidSpill}. %%% Fix a cmp op. do_cmp(I, TempMap, Strategy) -> #cmp{src=Src0,dst=Dst0} = I, {FixSrc, Src, FixDst, Dst, DidSpill} = do_binary(Src0, Dst0, TempMap, Strategy), {FixSrc ++ FixDst ++ [I#cmp{src=Src,dst=Dst}], DidSpill}. %%% Fix an imul op. do_imul(I, TempMap, Strategy) -> #imul{imm_opt=ImmOpt,src=Src0,temp=Temp0} = I, {FixSrc,Src,DidSpill1} = fix_src_operand(Src0, TempMap, Strategy), % temp1 {FixTempSrc,Temp,FixTempDst,DidSpill2} = case is_spilled(Temp0, TempMap) of false -> {[], Temp0, [], false}; true -> Reg = spill_temp0('untagged', Strategy), {case ImmOpt of [] -> [hipe_x86:mk_move(Temp0, Reg)]; % temp *= src _ -> [] % temp = src * imm end, Reg, [hipe_x86:mk_move(Reg, Temp0)], true} end, {FixSrc ++ FixTempSrc ++ [I#imul{src=Src,temp=Temp}] ++ FixTempDst, DidSpill1 or DidSpill2}. %%% Fix a jmp_switch op. -ifdef(HIPE_AMD64). do_jmp_switch(I, TempMap, Strategy) -> #jmp_switch{temp=Temp, jtab=Tab} = I, case is_spilled(Temp, TempMap) of false -> case is_spilled(Tab, TempMap) of false -> {[I], false}; true -> NewTab = spill_temp('untagged', Strategy), {[hipe_x86:mk_move(Tab, NewTab), I#jmp_switch{jtab=Tab}], true} end; true -> case is_spilled(Tab, TempMap) of false -> NewTmp = spill_temp('untagged', Strategy), {[hipe_x86:mk_move(Temp, NewTmp), I#jmp_switch{temp=NewTmp}], true}; true -> NewTmp = spill_temp('untagged', Strategy), NewTab = spill_temp0('untagged', Strategy), {[hipe_x86:mk_move(Temp, NewTmp), hipe_x86:mk_move(Tab, NewTab), I#jmp_switch{temp=NewTmp, jtab=NewTab}], true} end end. -else. % not AMD64 do_jmp_switch(I, TempMap, Strategy) -> #jmp_switch{temp=Temp} = I, case is_spilled(Temp, TempMap) of false -> {[I], false}; true -> NewTmp = spill_temp('untagged', Strategy), {[hipe_x86:mk_move(Temp, NewTmp), I#jmp_switch{temp=NewTmp}], true} end. -endif. % not AMD64 %%% Fix a lea op. do_lea(I, TempMap, Strategy) -> #lea{mem=Mem0,temp=Temp0} = I, {FixMem, Mem, DidSpill1} = fix_mem_operand(Mem0, TempMap, temp1(Strategy)), case Mem of #x86_mem{base=Base, off=#x86_imm{value=0}} -> %% We've decayed into a move due to both operands being memory (there's an %% 'add' in FixMem). {FixMem ++ [hipe_x86:mk_move(Base, Temp0)], DidSpill1}; #x86_mem{} -> {StoreTemp, Temp, DidSpill2} = case is_mem_opnd(Temp0, TempMap) of false -> {[], Temp0, false}; true -> Temp1 = clone2(Temp0, temp0(Strategy)), {[hipe_x86:mk_move(Temp1, Temp0)], Temp1, true} end, {FixMem ++ [I#lea{mem=Mem,temp=Temp} | StoreTemp], DidSpill1 or DidSpill2} end. %%% Fix a move op. do_move(I, TempMap, Strategy) -> #move{src=Src0,dst=Dst0} = I, case is_record(Src0, x86_temp) andalso is_record(Dst0, x86_temp) andalso is_spilled(Src0, TempMap) andalso is_spilled(Dst0, TempMap) of true -> Tmp = clone(Src0, Strategy), {[hipe_x86:mk_pseudo_spill_move(Src0, Tmp, Dst0)], true}; false -> {FixSrc, Src, FixDst, Dst, DidSpill} = do_check_byte_move(Src0, Dst0, TempMap, Strategy), {FixSrc ++ FixDst ++ [I#move{src=Src,dst=Dst}], DidSpill} end. -ifdef(HIPE_AMD64). %%% AMD64 has no issues with byte moves. do_check_byte_move(Src0, Dst0, TempMap, Strategy) -> do_binary(Src0, Dst0, TempMap, Strategy). -else. % not AMD64 %%% x86 can only do byte moves to a subset of the integer registers. do_check_byte_move(Src0, Dst0, TempMap, Strategy) -> case Dst0 of #x86_mem{type=byte} -> do_byte_move(Src0, Dst0, TempMap, Strategy); _ -> do_binary(Src0, Dst0, TempMap, Strategy) end. do_byte_move(Src0, Dst0, TempMap, Strategy) -> {FixSrc, Src, DidSpill1} = fix_src_operand(Src0, TempMap, Strategy), {FixDst, Dst, DidSpill2} = fix_dst_operand(Dst0, TempMap, Strategy), Reg = hipe_x86_registers:eax(), {FixSrc3, Src3} = % XXX: this just checks Src, the result is known! case Src of #x86_imm{} -> {FixSrc, Src}; #x86_temp{reg=Reg} -> % small moves must start from reg 1->4 {FixSrc, Src} % so variable sources are always put in eax end, {FixSrc3, Src3, FixDst, Dst, DidSpill2 or DidSpill1}. -endif. % not AMD64 %%% Fix a move64 op. do_move64(I, TempMap, Strategy) -> #move64{dst=Dst} = I, case is_spilled(Dst, TempMap) of false -> {[I], false}; true -> Reg = clone(Dst, Strategy), {[I#move64{dst=Reg}, hipe_x86:mk_move(Reg, Dst)], true} end. %%% Fix a movx op. do_movx(I, TempMap, Strategy) -> {{FixSrc, Src, DidSpill1}, {FixDst, Dst, DidSpill2}} = case I of #movsx{src=Src0,dst=Dst0} -> {fix_src_operand(Src0, TempMap, Strategy), fix_dst_operand(Dst0, TempMap, Strategy)}; #movzx{src=Src0,dst=Dst0} -> {fix_src_operand(Src0, TempMap, Strategy), fix_dst_operand(Dst0, TempMap, Strategy)} end, {I3, DidSpill3} = case is_spilled(Dst, TempMap) of false -> I2 = case I of #movsx{} -> [hipe_x86:mk_movsx(Src, Dst)]; #movzx{} -> [hipe_x86:mk_movzx(Src, Dst)] end, {I2, false}; true -> Dst2 = clone(Dst, Strategy), I2 = case I of #movsx{} -> [hipe_x86:mk_movsx(Src, Dst2), hipe_x86:mk_move(Dst2, Dst)]; #movzx{} -> [hipe_x86:mk_movzx(Src, Dst2), hipe_x86:mk_move(Dst2, Dst)] end, {I2, true} end, {FixSrc++FixDst++I3, DidSpill3 or DidSpill2 or DidSpill1}. %%% Fix an fmove op. do_fmove(I, TempMap, Strategy) -> #fmove{src=Src0,dst=Dst0} = I, {FixSrc, Src, DidSpill1} = fix_src_operand(Src0, TempMap, Strategy), {FixDst, Dst, DidSpill2} = fix_dst_operand(Dst0, TempMap, Strategy), %% fmoves from memory position to memory position is handled %% by the f.p. register allocator. {FixSrc ++ FixDst ++ [I#fmove{src=Src,dst=Dst}], DidSpill1 or DidSpill2}. %%% Fix an pseudo_spill_move op. do_pseudo_spill_move(I = #pseudo_spill_move{temp=Temp}, TempMap, _Strategy) -> %% Temp is above the low water mark and must not have been spilled false = is_spilled(Temp, TempMap), {[I], false}. % nothing to do %%% Fix a shift operation. %%% 1. remove pseudos from any explicit memory operands %%% 2. if the source is a register or memory position %%% make sure to move it to %ecx do_shift(I, TempMap, Strategy) -> #shift{src=Src0,dst=Dst0} = I, {FixDst, Dst, DidSpill} = fix_dst_operand(Dst0, TempMap, Strategy), Reg = ?HIPE_X86_REGISTERS:?ECX(), case Src0 of #x86_imm{} -> {FixDst ++ [I#shift{dst=Dst}], DidSpill}; #x86_temp{reg=Reg} -> {FixDst ++ [I#shift{dst=Dst}], DidSpill} end. %%% Fix a test op. do_test(I, TempMap, Strategy) -> #test{src=Src0,dst=Dst0} = I, {FixSrc, Src, FixDst, Dst, DidSpill} = do_binary(Src0, Dst0, TempMap, Strategy), {FixSrc ++ FixDst ++ [I#test{src=Src,dst=Dst}], DidSpill}. %%% Fix the operands of a binary op. %%% 1. remove pseudos from any explicit memory operands %%% 2. if both operands are (implicit or explicit) memory operands, %%% move src to a reg and use reg as src in the original insn do_binary(Src0, Dst0, TempMap, Strategy) -> {FixSrc, Src, DidSpill1} = fix_src_operand(Src0, TempMap, Strategy), {FixDst, Dst, DidSpill2} = fix_dst_operand(Dst0, TempMap, Strategy), {FixSrc3, Src3, DidSpill3} = case is_mem_opnd(Src, TempMap) of false -> {FixSrc, Src, false}; true -> case is_mem_opnd(Dst, TempMap) of false -> {FixSrc, Src, false}; true -> Src2 = clone(Src, Strategy), FixSrc2 = FixSrc ++ [hipe_x86:mk_move(Src, Src2)], {FixSrc2, Src2, true} end end, {FixSrc3, Src3, FixDst, Dst, DidSpill3 or DidSpill2 or DidSpill1}. %%% Fix any x86_mem operand to not refer to any spilled temps. fix_src_operand(Opnd, TmpMap, Strategy) -> fix_mem_operand(Opnd, TmpMap, temp1(Strategy)). temp1('normal') -> []; temp1('linearscan') -> ?HIPE_X86_REGISTERS:temp1(). fix_dst_operand(Opnd, TempMap, Strategy) -> fix_mem_operand(Opnd, TempMap, temp0(Strategy)). temp0('normal') -> []; temp0('linearscan') -> ?HIPE_X86_REGISTERS:temp0(). fix_mem_operand(Opnd, TempMap, RegOpt) -> % -> {[fixupcode], newop, DidSpill} case Opnd of #x86_mem{base=Base,off=Off} -> case is_mem_opnd(Base, TempMap) of false -> case is_mem_opnd(Off, TempMap) of false -> {[], Opnd, false}; true -> Temp = clone2(Off, RegOpt), {[hipe_x86:mk_move(Off, Temp)], Opnd#x86_mem{off=Temp}, true} end; true -> Temp = clone2(Base, RegOpt), case is_mem_opnd(Off, TempMap) of false -> % imm/reg(pseudo) {[hipe_x86:mk_move(Base, Temp)], Opnd#x86_mem{base=Temp}, true}; true -> % pseudo(pseudo) {[hipe_x86:mk_move(Base, Temp), hipe_x86:mk_alu('add', Off, Temp)], Opnd#x86_mem{base=Temp, off=hipe_x86:mk_imm(0)}, true} end end; _ -> {[], Opnd, false} end. %%% Check if an operand denotes a memory cell (mem or pseudo). is_mem_opnd(Opnd, TempMap) -> R = case Opnd of #x86_mem{} -> true; #x86_temp{} -> Reg = hipe_x86:temp_reg(Opnd), case hipe_x86:temp_is_allocatable(Opnd) of true -> case hipe_temp_map:is_spilled(Reg, TempMap) of true -> ?count_temp(Reg), true; false -> false end; false -> true end; _ -> false end, %% io:format("Op ~w mem: ~w\n",[Opnd,R]), R. %%% Check if an operand is a spilled Temp. is_spilled(Temp, TempMap) -> case hipe_x86:temp_is_allocatable(Temp) of true -> Reg = hipe_x86:temp_reg(Temp), case hipe_temp_map:is_spilled(Reg, TempMap) of true -> ?count_temp(Reg), true; false -> false end; false -> true end. %%% Make Reg a clone of Dst (attach Dst's type to Reg). clone(Dst, Strategy) -> Type = case Dst of #x86_mem{} -> hipe_x86:mem_type(Dst); #x86_temp{} -> hipe_x86:temp_type(Dst) end, spill_temp(Type, Strategy). spill_temp0(Type, 'normal') when Type =/= double -> hipe_x86:mk_new_temp(Type); spill_temp0(Type, 'linearscan') when Type =/= double -> hipe_x86:mk_temp(?HIPE_X86_REGISTERS:temp0(), Type). spill_temp(Type, 'normal') when Type =/= double -> hipe_x86:mk_new_temp(Type); spill_temp(Type, 'linearscan') when Type =/= double -> hipe_x86:mk_temp(?HIPE_X86_REGISTERS:temp1(), Type). %%% Make a certain reg into a clone of Dst clone2(Dst, RegOpt) -> Type = case Dst of #x86_mem{} -> hipe_x86:mem_type(Dst); #x86_temp{} -> hipe_x86:temp_type(Dst) end, case RegOpt of [] when Type =/= double -> hipe_x86:mk_new_temp(Type); Reg -> hipe_x86:mk_temp(Reg, Type) end.