123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294(* Assembly printer for ARM Cortex M4 (ARMv7-M).
We always use the Unified Assembly Language (UAL).
Immediate values (denoted <imm>) are always nonnegative integers.
*)openArch_declopenUtilsopenPrintCommonopenPrintASMopenProgopenAsm_utils(* Architecture imports*)openArm_declopenArm_instr_declopenArm_expand_immletarch=arm_declletimm_pre="#"(* We support the following ARMv7-M memory accesses.
Offset addressing:
- A base register and an immediate offset (displacement):
[<reg>, #+/-<imm>] (where + can be omitted).
- A base register and a register offset: [<reg>, <reg>].
- A base register and a scaled register offset: [<reg>, <reg>, LSL #<imm>].
*)letpp_reg_address_auxbasedispoffscal=match(disp,off,scal)with|None,None,None->Format.asprintf"[%s]"base|Somedisp,None,None->Format.asprintf"[%s, %s%s]"baseimm_predisp|None,Someoff,None->Format.asprintf"[%s, %s]"baseoff|None,Someoff,Somescal->Format.asprintf"[%s, %s, lsl %s%s]"baseoffimm_prescal|_,_,_->hierror~loc:Lnone~kind:"assembly printing"~internal:true"the address computation is too complex: an intermediate variable might be needed"letpp_braces=Format.asprintf"{%s}"sletpp_imm=pp_immimm_preletpp_register=pp_registerarchletpp_reg_addressaddr=letaddr=parse_reg_addressarchaddrinpp_reg_address_auxaddr.baseaddr.displacementaddr.offsetaddr.scaleletpp_condt=hash_to_stringstring_of_condtletpp_asm_arg(arg:(register,Arch_utils.empty,Arch_utils.empty,rflag,condt)asm_arg)=matchargwith|Condt_->None|Imm(ws,w)->Some(pp_imm(Conv.z_unsigned_of_wordwsw))|Regr->Some(pp_registerr)|Regx_->.|Addr(Aregra)->Some(pp_reg_addressra)|Addr(Aripr)->Some(pp_rip_addressr)|XReg_->.(* -------------------------------------------------------------------- *)(* TODO_ARM: Review. *)letheaders=[Instr(".thumb",[]);Instr(".syntax unified",[])](* -------------------------------------------------------------------- *)letpp_set_flagsopts=ifopts.set_flagsthen"s"else""(* We assume the only condition in the argument list is the one we need to
print. *)letpp_conditionalargs=matchList.opick(is_Condtarch)argswith|Somect->pp_condtct|None->""letpp_shift_kind=hash_to_stringstring_of_shift_kindletpp_shift(ARM_op(_,opts))args=matchopts.has_shiftwith|None->args|Somesk->letsh=pp_shift_kindskinList.modify_last(Format.asprintf"%s %s"sh)argsletpp_mnemonic_ext(ARM_op(_,opts)asop)suffargs=letid=instr_descArm_decl.arm_declArm_instr_decl.arm_op_decl(None,op)inletpp=id.id_pp_asmargsinFormat.asprintf"%s%s%s%s"pp.pp_aop_namesuff(pp_set_flagsopts)(pp_conditionalargs)(* To conform to the Unified Assembly Language (UAL) of ARM, IT instructions
must be introduced *in addition* to conditional suffixes. *)letget_ITi=matchiwith|AsmOp(_,args)->beginmatchList.opick(is_Condtarch)argswith|None->[]|Somec->[Instr("it",[pp_condtc])]end|_->[]moduleArgChecker:sig(* Return the (possibly empty) suffix for the mnemonic according to its
arguments.
Raise an error if the arguments are invalid. *)valcheck_args:arm_op->(Wsize.wsize*(register,Arch_utils.empty,Arch_utils.empty,rflag,condt)asm_arg)list->stringend=structletexn_imm_too_bign=hierror~loc:Lnone~kind:"printing""invalid immediate (%s is too large)."(Z.to_string(Conv.z_of_czn))letexn_imm_shiftedn=hierror~loc:Lnone~kind:"printing""unsupported immediate (%s needs a shift with carry)."(Z.to_string(Conv.z_of_czn))letchk_immargsnon_shifton_none=matchList.atargsnwith|_,Imm(_,w)->(letn=Word0.wunsignedWsize.U32winmatchei_kindnwith|EI_shift->on_shiftn|EI_none->on_nonen|_->"")|_->""letchk_w12_encodingoptsn=ifopts.set_flags||not(is_w12_encodingn)thenexn_imm_too_bignelse"w"letchk_w16_encodingoptsn=ifopts.set_flags||not(is_w16_encodingn)thenexn_imm_too_bignelse"w"(* Accept [EI_shift], reject [EI_none]. *)letchk_imm_accept_shiftargsn=chk_immargsn(fun_->"")exn_imm_too_big(* Accept [EI_shift], force W-encoding of 12-bits on [EI_none]. *)letchk_imm_accept_shift_w12argsnopts=chk_immargsn(fun_->"")(chk_w12_encodingopts)(* Reject [EI_shift] and [EI_none]. *)letchk_imm_reject_shiftargsn=chk_immargsnexn_imm_shiftedexn_imm_too_big(* We need to avoid encoding T2 when the constant is a shift to avoid setting
the carry flag.
We force the W-encoding of 16-bits on both [EI_shift] and [EI_none]. *)letchk_imm_w16_encodingargsnopts=chk_immargsn(chk_w16_encodingopts)(chk_w16_encodingopts)letcheck_args(ARM_op(mn,opts))args=matchmnwith|ADC|SBC|RSB->chk_imm_accept_shiftargs2|CMP|CMN->chk_imm_accept_shiftargs1|ADD|SUB->chk_imm_accept_shift_w12args2opts|MOV->chk_imm_w16_encodingargs1opts|AND|BIC|EOR|ORR->chk_imm_reject_shiftargs2|MVN|TST->chk_imm_reject_shiftargs1|_->""end(* Split an [ADR] instruction to a global symbol into a [MOVW]/[MOVT] pair. *)letpp_ADRppoptsargs=letname_lo=pp_mnemonic_ext(ARM_op(MOV,opts))"w"argsinletname_hi=pp_mnemonic_ext(ARM_op(MOVT,opts))""argsinletargs=List.filter_map(fun(_,a)->pp_asm_arga)pp.pp_aop_argsinletargs_lo,args_hi=matchargswith|dst::addr::rest->letlo="#:lower16:"^addrinlethi="#:upper16:"^addrin(dst::lo::rest,dst::hi::rest)|_->assertfalsein[Instr(name_lo,args_lo);Instr(name_hi,args_hi)]letarch=arm_declmoduleArmTarget:AsmTargetBuilder.AsmTargetwithtypereg=Arm_decl.registerandtyperegx=Arch_utils.emptyandtypexreg=Arch_utils.emptyandtyperflag=Arm_decl.rflagandtypecond=Arm_decl.condtandtypeasm_op=arm_op=structtypereg=Arm_decl.registertyperegx=Arch_utils.emptytypexreg=Arch_utils.emptytyperflag=Arm_decl.rflagtypecond=Arm_decl.condttypeasm_op=arm_opletheaders=[Instr(".thumb",[]);Instr(".syntax unified",[])]letdata_segment_header=[Instr(".p2align",["5"]);Labelglobal_datas_label]letfunction_tail=(* TODO_ARM: Review. *)[Instr("pop",["{pc}"])]letfunction_header=[Instr("push",[pp_brace(pp_registerLR)])]letpp_instr_rfni=matchiwith|ALIGN->failwith"TODO_ARM: pp_instr align"|LABEL(_,lbl)->[Label(string_of_labelfnlbl)]|STORELABEL(dst,lbl)->[Instr("adr",[pp_registerdst;string_of_labelfnlbl])]|JMPlbl->[Instr("b",[pp_remote_labellbl])]|JMPIarg->(* TODO_ARM: Review. *)letlbl=matchargwith|Regr->pp_registerr|_->failwith"TODO_ARM: pp_instr jmpi"in[Instr("bx",[lbl])]|Jcc(lbl,ct)->letiname=Format.asprintf"b%s"(pp_condtct)in[Instr(iname,[string_of_labelfnlbl])]|JAL(LR,lbl)->[Instr("bl",[pp_remote_labellbl])]|CALL_|JAL_->assertfalse|POPPC->[Instr("pop",["{pc}"])]|SysCallop->[Instr("bl",[pp_syscallop])]|AsmOp(op,args)->letid=instr_descarm_declarm_op_decl(None,op)inletpp=id.id_pp_asmargsin(* We need to perform the check even if we don't use the suffix, for
instance for [LDR] or [STR]. *)letsuff=ArgChecker.check_argsoppp.pp_aop_argsinmatchop,argswith|ARM_op(ADR,opts),_::Addr(Arip_)::_->pp_ADRppoptsargs|_,_->letname=pp_mnemonic_extopsuffargsinletargs=List.filter_map(fun(_,a)->pp_asm_arga)pp.pp_aop_argsinletargs=pp_shiftopargsinget_ITi@[Instr(name,args)]endmoduleArmBuilder=AsmTargetBuilder.Make(ArmTarget)letprint_progfmtprog=PrintASM.pp_asmfmt(ArmBuilder.asm_of_progprog)