123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404(****************************************************************************)(* Sail *)(* *)(* Sail and the Sail architecture models here, comprising all files and *)(* directories except the ASL-derived Sail code in the aarch64 directory, *)(* are subject to the BSD two-clause licence below. *)(* *)(* The ASL derived parts of the ARMv8.3 specification in *)(* aarch64/no_vector and aarch64/full are copyright ARM Ltd. *)(* *)(* Copyright (c) 2013-2021 *)(* Kathyrn Gray *)(* Shaked Flur *)(* Stephen Kell *)(* Gabriel Kerneis *)(* Robert Norton-Wright *)(* Christopher Pulte *)(* Peter Sewell *)(* Alasdair Armstrong *)(* Brian Campbell *)(* Thomas Bauereiss *)(* Anthony Fox *)(* Jon French *)(* Dominic Mulligan *)(* Stephen Kell *)(* Mark Wassell *)(* Alastair Reid (Arm Ltd) *)(* *)(* All rights reserved. *)(* *)(* This work was partially supported by EPSRC grant EP/K008528/1 <a *)(* href="http://www.cl.cam.ac.uk/users/pes20/rems">REMS: Rigorous *)(* Engineering for Mainstream Systems</a>, an ARM iCASE award, EPSRC IAA *)(* KTF funding, and donations from Arm. This project has received *)(* funding from the European Research Council (ERC) under the European *)(* Union’s Horizon 2020 research and innovation programme (grant *)(* agreement No 789108, ELVER). *)(* *)(* This software was developed by SRI International and the University of *)(* Cambridge Computer Laboratory (Department of Computer Science and *)(* Technology) under DARPA/AFRL contracts FA8650-18-C-7809 ("CIFV") *)(* and FA8750-10-C-0237 ("CTSRD"). *)(* *)(* Redistribution and use in source and binary forms, with or without *)(* modification, are permitted provided that the following conditions *)(* are met: *)(* 1. Redistributions of source code must retain the above copyright *)(* notice, this list of conditions and the following disclaimer. *)(* 2. Redistributions in binary form must reproduce the above copyright *)(* notice, this list of conditions and the following disclaimer in *)(* the documentation and/or other materials provided with the *)(* distribution. *)(* *)(* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' *)(* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *)(* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *)(* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR *)(* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *)(* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *)(* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *)(* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *)(* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *)(* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT *)(* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF *)(* SUCH DAMAGE. *)(****************************************************************************)openAstopenAst_utilopenType_checkopenRewritermoduleStringMap=Map.Make(String)(* Flag controls whether any constant folding will occur.
false = no folding, true = perform constant folding. *)letoptimize_constant_fold=reffalseletrecfexp_of_ctor(field,value)=FE_aux(FE_fexp(mk_idfield,exp_of_valuevalue),no_annot)(* The interpreter will return a value for each folded expression, so
we must convert that back to expression to re-insert it in the AST
*)andexp_of_value=letopenValueinfunction|V_intn->mk_lit_exp(L_numn)|V_bitSail_lib.B0->mk_lit_expL_zero|V_bitSail_lib.B1->mk_lit_expL_one|V_booltrue->mk_lit_expL_true|V_boolfalse->mk_lit_expL_false|V_stringstr->mk_lit_exp(L_stringstr)|V_recordfields->mk_exp(E_struct(List.mapfexp_of_ctor(StringMap.bindingsfields)))|V_vectorvs->mk_exp(E_vector(List.mapexp_of_valuevs))|V_tuplevs->mk_exp(E_tuple(List.mapexp_of_valuevs))|V_unit->mk_lit_expL_unit|V_attempted_readstr->mk_exp(E_id(mk_idstr))|_->failwith"No expression for value"(* A simple heuristic to avoid generating overly large literals. Note
that we avoid traversing through every element of vectors and
lists, so a list of large lists could still sneak through *)letrecis_too_large=letopenValueinfunction|V_int_|V_bit_|V_bool_|V_string_|V_unit|V_attempted_read_|V_real_|V_ref_|V_member_->false|V_vectorvs|V_tuplevs|V_listvs->List.compare_length_withvs256>0|V_recordfields->StringMap.exists(fun_v->is_too_largev)fields|V_ctor(_,vs)->List.existsis_too_largevs(* We want to avoid evaluating things like print statements at compile
time, so we remove them from this list of primops we can use when
constant folding. *)letsafe_primops=List.fold_left(funmk->StringMap.removekm)!Value.primops["print_endline";"prerr_endline";"putchar";"print";"prerr";"print_bits";"print_int";"print_string";"print_real";"prerr_bits";"prerr_int";"prerr_string";"read_ram";"write_ram";"get_time_ns";"Elf_loader.elf_entry";"Elf_loader.elf_tohost";](** We can specify a list of identifiers that we want to remove from
the final AST here. This is useful for removing tracing features in
optimized builds, e.g. for booting an OS as fast as possible.
Basically we just do this by mapping
f(x, y, z) -> ()
when f is in the list of identifiers to be mapped to unit. The
advantage of doing it like this is if x, y, and z are
computationally expensive then we remove them also. String
concatentation is very expensive at runtime so this is something we
really want when cutting out tracing features. Obviously it's
important that they don't have any meaningful side effects, and
that f does actually have type unit.
*)letopt_fold_to_unit=ref[]letfold_to_unitid=letremove=!opt_fold_to_unit|>List.mapmk_id|>List.fold_left(funmid->IdSet.addidm)IdSet.emptyinIdSet.memidremoveletrecis_constant(E_aux(e_aux,_)asexp)=matche_auxwith|E_lit_->true|E_vectorexps->List.for_allis_constantexps|E_structfexps->List.for_allis_constant_fexpfexps|E_typ(_,exp)->is_constantexp|E_tupleexps->List.for_allis_constantexps|E_idid->(matchEnv.lookup_idid(env_ofexp)withEnum_->true|_->false)|_->falseandis_constant_fexp(FE_aux(FE_fexp(_,exp),_))=is_constantexp(* Wrapper around interpreter that repeatedly steps until done. *)letrecrunframe=matchframewith|Interpreter.Done(state,v)->v|Interpreter.Fail_->(* something went wrong, raise exception to abort constant folding *)assertfalse|Interpreter.Step(lazy_str,_,_,_)->run(Interpreter.eval_frameframe)|Interpreter.Breakframe->run(Interpreter.eval_frameframe)|Interpreter.Effect_request(out,st,stack,Interpreter.Read_reg(reg,cont))->(* return a dummy value to read_reg requests which we handle above
if an expression finally evals to it, but the interpreter
will fail if it tries to actually use. See value.ml *)run(cont(Value.V_attempted_readreg)st)|Interpreter.Effect_request_->assertfalse(* effectful, raise exception to abort constant folding *)(** This rewriting pass looks for function applications (E_app)
expressions where every argument is a literal. It passes these
expressions to the OCaml interpreter in interpreter.ml, and
reconstructs the values returned back into expressions which are
then re-typechecked and re-inserted back into the AST.
We don't use the effect system to decide if expressions are safe to
evaluate, because this ignores I/O, and would force us to ignore
functions that maybe throw exceptions internally but as called are
totally safe. Instead any exceptions during evaluation are caught,
and the original expression is kept. Some causes of this could be:
- Function tries to read/write register.
- Calls an unsafe primop.
- Throws an exception that isn't caught.
*)letinitial_stateastenv=Interpreter.initial_state~registers:falseastenvsafe_primopstypefixed={registers:tannotexpBindings.t;fields:tannotexpBindings.tBindings.t}letno_fixed={registers=Bindings.empty;fields=Bindings.empty}letrw_expfixedtargetoknot_okistate=letevaluatee_auxannot=letinitial_monad=Interpreter.return(E_aux(e_aux,annot))intrybeginletv=run(Interpreter.Step(lazy"",istate,initial_monad,[]))inifnot(is_too_largev)then(letexp=exp_of_valuevintryok();Type_check.check_exp(env_of_annotannot)exp(typ_of_annotannot)withType_error.Type_error(l,err)->(* A type error here would be unexpected, so don't ignore it! *)Reporting.warn""l("Type error when folding constants in "^string_of_exp(E_aux(e_aux,annot))^"\n"^fst(Type_error.string_of_type_errorerr));not_ok();E_aux(e_aux,annot))elseE_aux(e_aux,annot)endwith(* Otherwise if anything goes wrong when trying to constant
fold, just continue without optimising. *)|_->E_aux(e_aux,annot)inletrw_funcalle_auxannot=matche_auxwith|E_app(id,args)whenfold_to_unitid->ok();E_aux(E_lit(L_aux(L_unit,fstannot)),annot)|E_idid->beginmatchBindings.find_optidfixed.registerswith|Someexp->ok();exp|None->E_aux(e_aux,annot)end|E_field(E_aux(E_idid,_),field)->beginmatchBindings.find_optidfixed.fieldswith|Somefields->beginmatchBindings.find_optfieldfieldswith|Someexp->ok();exp|None->E_aux(e_aux,annot)end|None->E_aux(e_aux,annot)end(* Short-circuit boolean operators with constants *)|E_app(id,[(E_aux(E_lit(L_aux(L_false,_)),_)asfalse_exp);_])whenstring_of_idid="and_bool"->ok();false_exp|E_app(id,[(E_aux(E_lit(L_aux(L_true,_)),_)astrue_exp);_])whenstring_of_idid="or_bool"->ok();true_exp|E_app(id,args)whenList.for_allis_constantargs->letenv=env_of_annotannotin(* We want to fold all primitive operations, but avoid folding
non-primitives that are defined in target-specific way. *)letis_primop=Env.is_externidenv"interpreter"&&StringMap.mem(Env.get_externidenv"interpreter")safe_primopsinif(not(Env.is_externidenvtarget))||is_primopthenevaluatee_auxannotelseE_aux(e_aux,annot)|E_typ(typ,(E_aux(E_lit_,_)aslit))->ok();lit|E_field(exp,id)whenis_constantexp->evaluatee_auxannot|E_if(E_aux(E_lit(L_aux(L_true,_)),_),then_exp,_)->ok();then_exp|E_if(E_aux(E_lit(L_aux(L_false,_)),_),_,else_exp)->ok();else_exp(* We only propagate lets in the simple case where we know that
the id will have the inferred type of the argument. For more
complex let bindings trying to propagate them may result in
type errors due to how type variables are bound by let bindings
*)|E_let(LB_aux(LB_val(P_aux(P_idid,_),bind),_),exp)whenis_constantbind->ok();substidbindexp|_->E_aux(e_aux,annot)infold_exp{id_exp_algwithe_aux=(fun(e_aux,annot)->rw_funcalle_auxannot)}letrewrite_exp_oncetarget=rw_expno_fixedtarget(fun_->())(fun_->())letrecrewrite_constant_function_calls'fixedtargetast=letrewrite_count=ref0inletok()=incrrewrite_countinletnot_ok()=decrrewrite_countinletistate=initial_stateastType_check.initial_envinletrw_defs={rewriters_basewithrewrite_exp=(fun_->rw_expfixedtargetoknot_okistate)}inletast=rewrite_ast_baserw_defsastin(* We keep iterating until we have no more re-writes to do *)if!rewrite_count>0thenrewrite_constant_function_calls'fixedtargetastelseastletrewrite_constant_function_callsfixedtargetast=if!optimize_constant_foldthenrewrite_constant_function_calls'fixedtargetastelseasttypeto_constant=Registerofid*typ*tannotexp|Register_fieldofid*id*typ*tannotexplet()=letopenInteractiveinletopenPrintfinletupdate_fixedfixed=function|Register(id,_,exp)->{fixedwithregisters=Bindings.addidexpfixed.registers}|Register_field(id,field,_,exp)->letprev_fields=matchBindings.find_optidfixed.fieldswithSomef->f|None->Bindings.emptyinletupdated_fields=Bindings.addfieldexpprev_fieldsin{fixedwithfields=Bindings.addidupdated_fieldsfixed.fields}inArgString("target",funtarget->ArgString("assignments",funassignments->Action(funistate->letassignments=Str.split(Str.regexp" +")assignmentsinletassignments=List.map(funassignment->matchString.split_on_char'='assignmentwith|[reg;value]->beginmatchString.split_on_char'.'regwith|[reg;field]->letreg=mk_idreginletfield=mk_idfieldinbeginmatchEnv.lookup_idregistate.envwith|Register(Typ_aux(Typ_idrec_id,_))->let_,fields=Env.get_recordrec_idistate.envinlettyp=matchList.find_opt(fun(typ,id)->Id.compareidfield=0)fieldswith|Some(typ,_)->typ|None->failwith(sprintf"Register %s does not have a field %s"(string_of_idreg)(string_of_idfield))inletexp=Initial_check.exp_of_stringvalueinletexp=check_expistate.envexptypinRegister_field(reg,field,typ,exp)|_->failwith(sprintf"Register %s is not defined as a record in the current environment"(string_of_idreg))end|_->letreg=mk_idreginbeginmatchEnv.lookup_idregistate.envwith|Registertyp->letexp=Initial_check.exp_of_stringvalueinletexp=check_expistate.envexptypinRegister(reg,typ,exp)|_->failwith(sprintf"Register %s is not defined in the current environment"(string_of_idreg))endend|_->failwith(sprintf"Could not parse '%s' as an assignment <register>=<value>"assignment))assignmentsinletassignments=List.fold_leftupdate_fixedno_fixedassignmentsin{istatewithast=rewrite_constant_function_calls'assignmentstargetistate.ast})))|>register_command~name:"fix_registers"~help:"Fix the value of specified registers, specified as a list of <register>=<value>. Can also fix a specific \
register field as <register>.<field>=<value>. Note that this is not used to set registers normally, but \
instead fixes their value such that the constant folding rewrite (which is subsequently invoked by this \
command) will replace register reads with the fixed values. Requires a target (c, lem, etc.), as the set of \
functions that can be constant folded can differ on a per-target basis."