[sledge] Statically resolve known function calls

Summary:
The callee function of a Call can often be resolved
statically. Currently this is resolution is only done dynamically
during symbolic execution by checking if the callee expression is a
function name and looking up the function in the program. This is
wasted and redundant work. Also, the static resolution code is
duplicated in all the domains.

This diff resolves this by resolving known callees statically at
translation time. This involves:

- add an ICall terminator that is the same as Call is currently

- change Call to use a func callee instead of Exp.t

- make callee field mutable since recursive calls can create cycles

- change the Llair.Term.call constructor to return a thunk to perform
  the backpatching once the callee has been translated

- modify the Frontend

  + to determine whether to emit Call or ICall depending on whether
    the callee in LLVM is already a Function

  + to record the LLVM function -- backpatch thunk pairs encountered during translation

  + record the mapping of LLVM to LLAIR functions during translation

  + to enumerate the calls to backpatch after all functions have been
    translated, and find the LLAIR function corresponding to each LLVM
    function and backpatch the call to use it as the callee

  + to handle direct calls to undefined functions, when backpatching
    translate such function declarations into undefined functions

Reviewed By: jvillard

Differential Revision: D25146152

fbshipit-source-id: 47d2ca1ff
master
Josh Berdine 4 years ago committed by Facebook GitHub Bot
parent 9e3ca541e8
commit 5c5126474e

@ -275,11 +275,7 @@ let call ~summaries ~globals:_ ~actuals ~areturn ~formals ~freturn:_
(q''', {areturn; caller_q= q}) (q''', {areturn; caller_q= q})
let dnf q = [q] let dnf q = [q]
let resolve_callee _ _ q = ([], q)
let resolve_callee lookup ptr q =
match Llair.Function.of_exp ptr with
| Some callee -> (lookup callee, q)
| None -> ([], q)
type summary = t type summary = t

@ -378,6 +378,10 @@ let pp_prefix_exp fs (insts, exp) =
of 'undef' to a distinct register *) of 'undef' to a distinct register *)
let undef_count = ref 0 let undef_count = ref 0
module GlobTbl = LlvalueTbl
let memo_global : GlobalDefn.t GlobTbl.t = GlobTbl.create ()
module ValTbl = HashTable.Make (struct module ValTbl = HashTable.Make (struct
type t = bool * Llvm.llvalue type t = bool * Llvm.llvalue
@ -386,10 +390,6 @@ end)
let memo_value : (Inst.t list * Exp.t) ValTbl.t = ValTbl.create () let memo_value : (Inst.t list * Exp.t) ValTbl.t = ValTbl.create ()
module GlobTbl = LlvalueTbl
let memo_global : GlobalDefn.t GlobTbl.t = GlobTbl.create ()
let should_inline : Llvm.llvalue -> bool = let should_inline : Llvm.llvalue -> bool =
fun llv -> fun llv ->
match Llvm.use_begin llv with match Llvm.use_begin llv with
@ -1015,6 +1015,22 @@ let xlate_intrinsic_inst emit_inst x name_segs instr num_actuals loc =
| None -> None ) | None -> None )
| _ -> None | _ -> None
let calls_to_backpatch = ref []
let term_call x llcallee ~typ ~actuals ~areturn ~return ~throw ~loc =
match Llvm.classify_value llcallee with
| Function ->
let name = Llvm.value_name llcallee in
let call, backpatch =
Term.call ~name ~typ ~actuals ~areturn ~return ~throw ~loc
in
calls_to_backpatch :=
(llcallee, typ, backpatch) :: !calls_to_backpatch ;
([], call)
| _ ->
let prefix, callee = xlate_value x llcallee in
(prefix, Term.icall ~callee ~typ ~actuals ~areturn ~return ~throw ~loc)
let xlate_instr : let xlate_instr :
pop_thunk pop_thunk
-> x -> x
@ -1073,9 +1089,9 @@ let xlate_instr :
| Call -> ( | Call -> (
let llcallee = Llvm.operand instr (Llvm.num_operands instr - 1) in let llcallee = Llvm.operand instr (Llvm.num_operands instr - 1) in
let lltyp = Llvm.type_of llcallee in let lltyp = Llvm.type_of llcallee in
let llfunc = norm_callee llcallee in let llcallee = norm_callee llcallee in
let num_actuals = num_actuals instr lltyp llfunc in let num_actuals = num_actuals instr lltyp llcallee in
let fname = Llvm.value_name llfunc in let fname = Llvm.value_name llcallee in
let name_segs = String.split_on_char fname ~by:'.' in let name_segs = String.split_on_char fname ~by:'.' in
let skip msg = let skip msg =
if StringS.add ignored_callees fname then if StringS.add ignored_callees fname then
@ -1111,32 +1127,30 @@ let xlate_instr :
| ["llvm"; ("va_start" | "va_copy" | "va_end")] -> | ["llvm"; ("va_start" | "va_copy" | "va_end")] ->
skip "variadic function intrinsic" skip "variadic function intrinsic"
| "llvm" :: _ -> skip "intrinsic" | "llvm" :: _ -> skip "intrinsic"
| _ when Poly.equal (Llvm.classify_value llfunc) InlineAsm -> | _ when Poly.equal (Llvm.classify_value llcallee) InlineAsm ->
skip "inline asm" skip "inline asm"
(* general function call that may not throw *) (* general function call that may not throw *)
| _ -> | _ ->
let pre0, callee = xlate_value x llfunc in
let typ = xlate_type x lltyp in let typ = xlate_type x lltyp in
let lbl = name ^ ".ret" in let lbl = name ^ ".ret" in
let pre, call = let pre_1, actuals =
let pre, actuals = xlate_values x num_actuals (Llvm.operand instr)
xlate_values x num_actuals (Llvm.operand instr) in
in let areturn = xlate_name_opt x instr in
let areturn = xlate_name_opt x instr in let return = Jump.mk lbl in
let return = Jump.mk lbl in let pre_0, call =
( pre term_call x llcallee ~typ ~actuals ~areturn ~return
, Term.call ~callee ~typ ~actuals ~areturn ~return ~throw:None ~loc
~throw:None ~loc )
in in
continue (fun (insts, term) -> continue (fun (insts, term) ->
let cmnd = IArray.of_list insts in let cmnd = IArray.of_list insts in
(pre0 @ pre, call, [Block.mk ~lbl ~cmnd ~term]) ) ) ) ) (pre_0 @ pre_1, call, [Block.mk ~lbl ~cmnd ~term]) ) ) ) )
| Invoke -> ( | Invoke -> (
let llcallee = Llvm.operand instr (Llvm.num_operands instr - 3) in let llcallee = Llvm.operand instr (Llvm.num_operands instr - 3) in
let lltyp = Llvm.type_of llcallee in let lltyp = Llvm.type_of llcallee in
let llfunc = norm_callee llcallee in let llcallee = norm_callee llcallee in
let num_actuals = num_actuals instr lltyp llfunc in let num_actuals = num_actuals instr lltyp llcallee in
let fname = Llvm.value_name llfunc in let fname = Llvm.value_name llcallee in
let name_segs = String.split_on_char fname ~by:'.' in let name_segs = String.split_on_char fname ~by:'.' in
let return_blk = Llvm.get_normal_dest instr in let return_blk = Llvm.get_normal_dest instr in
let unwind_blk = Llvm.get_unwind_dest instr in let unwind_blk = Llvm.get_unwind_dest instr in
@ -1169,11 +1183,10 @@ let xlate_instr :
(* unimplemented *) (* unimplemented *)
| "llvm" :: "experimental" :: "gc" :: "statepoint" :: _ -> | "llvm" :: "experimental" :: "gc" :: "statepoint" :: _ ->
todo "statepoints:@ %a" pp_llvalue instr () todo "statepoints:@ %a" pp_llvalue instr ()
| _ when Poly.equal (Llvm.classify_value llfunc) InlineAsm -> | _ when Poly.equal (Llvm.classify_value llcallee) InlineAsm ->
todo "inline asm: @ %a" pp_llvalue instr () todo "inline asm: @ %a" pp_llvalue instr ()
(* general function call that may throw *) (* general function call that may throw *)
| _ -> | _ ->
let pre_0, callee = xlate_value x llfunc in
let typ = xlate_type x lltyp in let typ = xlate_type x lltyp in
let pre_1, actuals = let pre_1, actuals =
xlate_values x num_actuals (Llvm.operand instr) xlate_values x num_actuals (Llvm.operand instr)
@ -1185,12 +1198,12 @@ let xlate_instr :
let pre_3, throw, blocks = let pre_3, throw, blocks =
xlate_jump x instr unwind_blk loc blocks xlate_jump x instr unwind_blk loc blocks
in in
let throw = Some throw in let pre_0, call =
emit_term term_call x llcallee ~typ ~actuals ~areturn ~return
~prefix:(List.concat [pre_0; pre_1; pre_2; pre_3]) ~throw:(Some throw) ~loc
(Term.call ~callee ~typ ~actuals ~areturn ~return ~throw in
~loc) let prefix = List.concat [pre_0; pre_1; pre_2; pre_3] in
~blocks ) ) ) emit_term ~prefix call ~blocks ) ) )
| Ret -> | Ret ->
let pre, exp = let pre, exp =
if Llvm.num_operands instr = 0 then ([], None) if Llvm.num_operands instr = 0 then ([], None)
@ -1396,6 +1409,16 @@ let xlate_instr :
fail "xlate_instr: %a" pp_llvalue instr () fail "xlate_instr: %a" pp_llvalue instr ()
| PHI | Invalid | Invalid2 | UserOp1 | UserOp2 -> assert false | PHI | Invalid | Invalid2 | UserOp1 | UserOp2 -> assert false
let rec xlate_instrs : pop_thunk -> x -> _ Llvm.llpos -> code =
fun pop x -> function
| Before instrI ->
xlate_instr pop x instrI (fun xlate_instrI ->
let instrJ = Llvm.instr_succ instrI in
let instsJ, termJ, blocksJN = xlate_instrs pop x instrJ in
let instsI, termI, blocksI = xlate_instrI (instsJ, termJ) in
(instsI, termI, blocksI @ blocksJN) )
| At_end blk -> fail "xlate_instrs: %a" pp_llblock blk ()
let skip_phis : Llvm.llbasicblock -> _ Llvm.llpos = let skip_phis : Llvm.llbasicblock -> _ Llvm.llpos =
fun blk -> fun blk ->
let rec skip_phis_ (pos : _ Llvm.llpos) = let rec skip_phis_ (pos : _ Llvm.llpos) =
@ -1408,16 +1431,6 @@ let skip_phis : Llvm.llbasicblock -> _ Llvm.llpos =
in in
skip_phis_ (Llvm.instr_begin blk) skip_phis_ (Llvm.instr_begin blk)
let rec xlate_instrs : pop_thunk -> x -> _ Llvm.llpos -> code =
fun pop x -> function
| Before instrI ->
xlate_instr pop x instrI (fun xlate_instrI ->
let instrJ = Llvm.instr_succ instrI in
let instsJ, termJ, blocksJN = xlate_instrs pop x instrJ in
let instsI, termI, blocksI = xlate_instrI (instsJ, termJ) in
(instsI, termI, blocksI @ blocksJN) )
| At_end blk -> fail "xlate_instrs: %a" pp_llblock blk ()
let xlate_block : pop_thunk -> x -> Llvm.llbasicblock -> Llair.block list = let xlate_block : pop_thunk -> x -> Llvm.llbasicblock -> Llair.block list =
fun pop x blk -> fun pop x blk ->
[%Trace.call fun {pf} -> pf "%a" pp_llblock blk] [%Trace.call fun {pf} -> pf "%a" pp_llblock blk]
@ -1433,16 +1446,11 @@ let report_undefined func name =
if Option.is_some (Llvm.use_begin func) then if Option.is_some (Llvm.use_begin func) then
[%Trace.info "undefined function: %a" Function.pp name] [%Trace.info "undefined function: %a" Function.pp name]
let xlate_function : x -> Llvm.llvalue -> Llair.func = let xlate_function_decl x llfunc typ k =
fun x llf -> let loc = find_loc llfunc in
[%Trace.call fun {pf} -> pf "%a" pp_llvalue llf] let name = Function.mk typ (find_name llfunc) in
;
undef_count := 0 ;
let loc = find_loc llf in
let typ = xlate_type x (Llvm.type_of llf) in
let name = Function.mk typ (find_name llf) in
let formals = let formals =
Iter.from_iter (fun f -> Llvm.iter_params f llf) Iter.from_iter (fun f -> Llvm.iter_params f llfunc)
|> Iter.map ~f:(xlate_name x) |> Iter.map ~f:(xlate_name x)
|> IArray.of_iter |> IArray.of_iter
in in
@ -1454,6 +1462,16 @@ let xlate_function : x -> Llvm.llvalue -> Llair.func =
in in
let _, _, exc_typ = exception_typs in let _, _, exc_typ = exception_typs in
let fthrow = Reg.mk exc_typ "fthrow" in let fthrow = Reg.mk exc_typ "fthrow" in
k ~name ~formals ~freturn ~fthrow ~loc
let xlate_function : x -> Llvm.llvalue -> Llair.func =
fun x llf ->
[%Trace.call fun {pf} -> pf "%a" pp_llvalue llf]
;
undef_count := 0 ;
let typ = xlate_type x (Llvm.type_of llf) in
xlate_function_decl x llf typ
@@ fun ~name ~formals ~freturn ~fthrow ~loc ->
( match Llvm.block_begin llf with ( match Llvm.block_begin llf with
| Before entry_blk -> | Before entry_blk ->
let pop = pop_stack_frame_of_function x llf entry_blk in let pop = pop_stack_frame_of_function x llf entry_blk in
@ -1482,6 +1500,18 @@ let xlate_function : x -> Llvm.llvalue -> Llair.func =
|> |>
[%Trace.retn fun {pf} -> pf "@\n%a" Func.pp] [%Trace.retn fun {pf} -> pf "@\n%a" Func.pp]
let backpatch_calls x func_tbl =
List.iter !calls_to_backpatch ~f:(fun (llfunc, typ, backpatch) ->
match LlvalueTbl.find func_tbl llfunc with
| Some callee -> backpatch ~callee
| None ->
xlate_function_decl x llfunc typ
@@ fun ~name ~formals ~freturn ~fthrow ~loc ->
let callee =
Func.mk_undefined ~name ~formals ~freturn ~fthrow ~loc
in
backpatch ~callee )
let transform ~internalize : Llvm.llmodule -> unit = let transform ~internalize : Llvm.llmodule -> unit =
fun llmodule -> fun llmodule ->
let pm = Llvm.PassManager.create () in let pm = Llvm.PassManager.create () in
@ -1558,13 +1588,14 @@ let check_datalayout llcontext lldatalayout =
Llvm.dispose_context. *) Llvm.dispose_context. *)
let cleanup llmodule llcontext = let cleanup llmodule llcontext =
SymTbl.clear sym_tbl ; SymTbl.clear sym_tbl ;
String.Tbl.clear realpath_tbl ;
ScopeTbl.clear scope_tbl ; ScopeTbl.clear scope_tbl ;
String.Tbl.clear realpath_tbl ;
LltypeTbl.clear anon_struct_name ; LltypeTbl.clear anon_struct_name ;
LltypeTbl.clear memo_type ; LltypeTbl.clear memo_type ;
GlobTbl.clear memo_global ; GlobTbl.clear memo_global ;
ValTbl.clear memo_value ; ValTbl.clear memo_value ;
StringS.clear ignored_callees ; StringS.clear ignored_callees ;
calls_to_backpatch := [] ;
Gc.full_major () ; Gc.full_major () ;
Llvm.dispose_module llmodule ; Llvm.dispose_module llmodule ;
Llvm.dispose_context llcontext Llvm.dispose_context llcontext
@ -1608,6 +1639,7 @@ let translate ~models ~fuzzer ~internalize : string list -> Llair.program =
else xlate_global x llg :: globals ) else xlate_global x llg :: globals )
[] llmodule [] llmodule
in in
let func_tbl : Func.t LlvalueTbl.t = LlvalueTbl.create () in
let functions = let functions =
Llvm.fold_left_functions Llvm.fold_left_functions
(fun functions llf -> (fun functions llf ->
@ -1616,9 +1648,13 @@ let translate ~models ~fuzzer ~internalize : string list -> Llair.program =
String.prefix name ~pre:"__llair_" String.prefix name ~pre:"__llair_"
|| String.prefix name ~pre:"llvm." || String.prefix name ~pre:"llvm."
then functions then functions
else xlate_function x llf :: functions ) else
let func = xlate_function x llf in
LlvalueTbl.set func_tbl ~key:llf ~data:func ;
func :: functions )
[] llmodule [] llmodule
in in
backpatch_calls x func_tbl ;
cleanup llmodule llcontext ; cleanup llmodule llcontext ;
Llair.Program.mk ~globals ~functions Llair.Program.mk ~globals ~functions
|> |>

@ -308,6 +308,24 @@ module Make (Dom : Domain_intf.Dom) = struct
|> |>
[%Trace.retn fun {pf} _ -> pf ""] [%Trace.retn fun {pf} _ -> pf ""]
let exec_skip_func :
Stack.t
-> Dom.t
-> Llair.block
-> Llair.Reg.t option
-> Llair.jump
-> Work.x =
fun stk state block areturn return ->
Report.unknown_call block.term ;
let state = Option.fold ~f:Dom.exec_kill areturn state in
exec_jump stk state block return
let exec_call opts stk state block
({Llair.callee; areturn; return; _} as call) globals =
if Llair.Func.is_undefined callee then
exec_skip_func stk state block areturn return
else exec_call opts stk state block call globals
let pp_st () = let pp_st () =
[%Trace.printf [%Trace.printf
"@[<v>%t@]" (fun fs -> "@[<v>%t@]" (fun fs ->
@ -376,18 +394,6 @@ module Make (Dom : Domain_intf.Dom) = struct
|> |>
[%Trace.retn fun {pf} _ -> pf ""] [%Trace.retn fun {pf} _ -> pf ""]
let exec_skip_func :
Stack.t
-> Dom.t
-> Llair.block
-> Llair.Reg.t option
-> Llair.jump
-> Work.x =
fun stk state block areturn return ->
Report.unknown_call block.term ;
let state = Option.fold ~f:Dom.exec_kill areturn state in
exec_jump stk state block return
let exec_term : let exec_term :
exec_opts exec_opts
-> Llair.program -> Llair.program
@ -425,7 +431,10 @@ module Make (Dom : Domain_intf.Dom) = struct
with with
| Some state -> exec_jump stk state block jump |> Work.seq x | Some state -> exec_jump stk state block jump |> Work.seq x
| None -> x ) | None -> x )
| Call ({callee; areturn; return} as call) -> ( | Call ({callee} as call) ->
exec_call opts stk state block call
(Domain_used_globals.by_function opts.globals callee.name)
| ICall ({callee; areturn; return} as call) -> (
let lookup name = let lookup name =
Option.to_list (Llair.Func.find name pgm.functions) Option.to_list (Llair.Func.find name pgm.functions)
in in
@ -434,12 +443,8 @@ module Make (Dom : Domain_intf.Dom) = struct
| [] -> exec_skip_func stk state block areturn return | [] -> exec_skip_func stk state block areturn return
| callees -> | callees ->
List.fold callees Work.skip ~f:(fun callee x -> List.fold callees Work.skip ~f:(fun callee x ->
( if Llair.Func.is_undefined callee then exec_call opts stk state block {call with callee}
exec_skip_func stk state block areturn return (Domain_used_globals.by_function opts.globals callee.name)
else
exec_call opts stk state block {call with callee}
(Domain_used_globals.by_function opts.globals
callee.name) )
|> Work.seq x ) ) |> Work.seq x ) )
| Return {exp} -> exec_return ~opts stk state block exp | Return {exp} -> exec_return ~opts stk state block exp
| Throw {exc} -> | Throw {exc} ->

@ -29,11 +29,7 @@ let recursion_beyond_bound = `skip
let post _ _ () = () let post _ _ () = ()
let retn _ _ _ _ = () let retn _ _ _ _ = ()
let dnf () = [()] let dnf () = [()]
let resolve_callee _ _ q = ([], q)
let resolve_callee lookup ptr _ =
match Llair.Function.of_exp ptr with
| Some callee -> (lookup callee, ())
| None -> ([], ())
type summary = unit type summary = unit

@ -52,11 +52,7 @@ let call ~summaries:_ ~globals:_ ~actuals ~areturn:_ ~formals:_ ~freturn:_
~locals:_ st = ~locals:_ st =
(empty, IArray.fold ~f:used_globals actuals st) (empty, IArray.fold ~f:used_globals actuals st)
let resolve_callee lookup ptr st = let resolve_callee _ _ q = ([], q)
let st = used_globals ptr st in
match Llair.Function.of_exp ptr with
| Some callee -> (lookup callee, st)
| None -> ([], st)
(* A function summary is the set of global registers accessed by that (* A function summary is the set of global registers accessed by that
function and its transitive callees *) function and its transitive callees *)

@ -53,7 +53,7 @@ type label = string [@@deriving compare, equal, hash, sexp]
type jump = {mutable dst: block; mutable retreating: bool} type jump = {mutable dst: block; mutable retreating: bool}
and 'a call = and 'a call =
{ callee: 'a { mutable callee: 'a
; typ: Typ.t ; typ: Typ.t
; actuals: Exp.t iarray ; actuals: Exp.t iarray
; areturn: Reg.t option ; areturn: Reg.t option
@ -65,7 +65,8 @@ and 'a call =
and term = and term =
| Switch of {key: Exp.t; tbl: (Exp.t * jump) iarray; els: jump; loc: Loc.t} | Switch of {key: Exp.t; tbl: (Exp.t * jump) iarray; els: jump; loc: Loc.t}
| Iswitch of {ptr: Exp.t; tbl: jump iarray; loc: Loc.t} | Iswitch of {ptr: Exp.t; tbl: jump iarray; loc: Loc.t}
| Call of Exp.t call | Call of func call
| ICall of Exp.t call
| Return of {exp: Exp.t option; loc: Loc.t} | Return of {exp: Exp.t option; loc: Loc.t}
| Throw of {exc: Exp.t; loc: Loc.t} | Throw of {exc: Exp.t; loc: Loc.t}
| Unreachable | Unreachable
@ -112,7 +113,7 @@ with type jump := jump
[@@deriving compare, equal] [@@deriving compare, equal]
type nonrec 'a call = 'a call = type nonrec 'a call = 'a call =
{ callee: 'a { mutable callee: 'a
; typ: Typ.t ; typ: Typ.t
; actuals: Exp.t iarray ; actuals: Exp.t iarray
; areturn: Reg.t option ; areturn: Reg.t option
@ -126,7 +127,8 @@ with type jump := jump
| Switch of | Switch of
{key: Exp.t; tbl: (Exp.t * jump) iarray; els: jump; loc: Loc.t} {key: Exp.t; tbl: (Exp.t * jump) iarray; els: jump; loc: Loc.t}
| Iswitch of {ptr: Exp.t; tbl: jump iarray; loc: Loc.t} | Iswitch of {ptr: Exp.t; tbl: jump iarray; loc: Loc.t}
| Call of Exp.t call | Call of func call
| ICall of Exp.t call
| Return of {exp: Exp.t option; loc: Loc.t} | Return of {exp: Exp.t option; loc: Loc.t}
| Throw of {exc: Exp.t; loc: Loc.t} | Throw of {exc: Exp.t; loc: Loc.t}
| Unreachable | Unreachable
@ -142,6 +144,19 @@ let hash_fold_jump s {dst; retreating} =
let s = [%hash_fold: bool] s retreating in let s = [%hash_fold: bool] s retreating in
s s
let hash_fold_call (type callee) hash_fold_callee s
{callee: callee; typ; actuals; areturn; return; throw; recursive; loc} =
let s = [%hash_fold: int] s 3 in
let s = [%hash_fold: callee] s callee in
let s = [%hash_fold: Typ.t] s typ in
let s = [%hash_fold: Exp.t iarray] s actuals in
let s = [%hash_fold: Reg.t option] s areturn in
let s = [%hash_fold: jump] s return in
let s = [%hash_fold: jump option] s throw in
let s = [%hash_fold: bool] s recursive in
let s = [%hash_fold: Loc.t] s loc in
s
let hash_fold_term s = function let hash_fold_term s = function
| Switch {key; tbl; els; loc} -> | Switch {key; tbl; els; loc} ->
let s = [%hash_fold: int] s 1 in let s = [%hash_fold: int] s 1 in
@ -156,28 +171,25 @@ let hash_fold_term s = function
let s = [%hash_fold: jump iarray] s tbl in let s = [%hash_fold: jump iarray] s tbl in
let s = [%hash_fold: Loc.t] s loc in let s = [%hash_fold: Loc.t] s loc in
s s
| Call {callee; typ; actuals; areturn; return; throw; recursive; loc} -> | Call call ->
let s = [%hash_fold: int] s 3 in let s = [%hash_fold: int] s 3 in
let s = [%hash_fold: Exp.t] s callee in let s = hash_fold_call hash_fold_func s call in
let s = [%hash_fold: Typ.t] s typ in
let s = [%hash_fold: Exp.t iarray] s actuals in
let s = [%hash_fold: Reg.t option] s areturn in
let s = [%hash_fold: jump] s return in
let s = [%hash_fold: jump option] s throw in
let s = [%hash_fold: bool] s recursive in
let s = [%hash_fold: Loc.t] s loc in
s s
| Return {exp; loc} -> | ICall call ->
let s = [%hash_fold: int] s 4 in let s = [%hash_fold: int] s 4 in
let s = hash_fold_call Exp.hash_fold_t s call in
s
| Return {exp; loc} ->
let s = [%hash_fold: int] s 5 in
let s = [%hash_fold: Exp.t option] s exp in let s = [%hash_fold: Exp.t option] s exp in
let s = [%hash_fold: Loc.t] s loc in let s = [%hash_fold: Loc.t] s loc in
s s
| Throw {exc; loc} -> | Throw {exc; loc} ->
let s = [%hash_fold: int] s 5 in let s = [%hash_fold: int] s 6 in
let s = [%hash_fold: Exp.t] s exc in let s = [%hash_fold: Exp.t] s exc in
let s = [%hash_fold: Loc.t] s loc in let s = [%hash_fold: Loc.t] s loc in
s s
| Unreachable -> [%hash_fold: int] s 6 | Unreachable -> [%hash_fold: int] s 7
let hash_func = Hash.of_fold hash_fold_func let hash_func = Hash.of_fold hash_fold_func
let hash_block = Hash.of_fold hash_fold_block let hash_block = Hash.of_fold hash_fold_block
@ -196,6 +208,19 @@ let sexp_ctor label args = sexp_cons (Sexp.Atom label) args
let sexp_of_jump {dst; retreating} = let sexp_of_jump {dst; retreating} =
[%sexp {dst: label = dst.lbl; retreating: bool}] [%sexp {dst: label = dst.lbl; retreating: bool}]
let sexp_of_call (type callee) tag sexp_of_callee
{callee: callee; typ; actuals; areturn; return; throw; recursive; loc} =
sexp_ctor tag
[%sexp
{ callee: callee
; typ: Typ.t
; actuals: Exp.t iarray
; areturn: Reg.t option
; return: jump
; throw: jump option
; recursive: bool
; loc: Loc.t }]
let sexp_of_term = function let sexp_of_term = function
| Switch {key; tbl; els; loc} -> | Switch {key; tbl; els; loc} ->
sexp_ctor "Switch" sexp_ctor "Switch"
@ -203,17 +228,9 @@ let sexp_of_term = function
{key: Exp.t; tbl: (Exp.t * jump) iarray; els: jump; loc: Loc.t}] {key: Exp.t; tbl: (Exp.t * jump) iarray; els: jump; loc: Loc.t}]
| Iswitch {ptr; tbl; loc} -> | Iswitch {ptr; tbl; loc} ->
sexp_ctor "Iswitch" [%sexp {ptr: Exp.t; tbl: jump iarray; loc: Loc.t}] sexp_ctor "Iswitch" [%sexp {ptr: Exp.t; tbl: jump iarray; loc: Loc.t}]
| Call {callee; typ; actuals; areturn; return; throw; recursive; loc} -> | Call call ->
sexp_ctor "Call" sexp_of_call "Call" (fun f -> Function.sexp_of_t f.name) call
[%sexp | ICall call -> sexp_of_call "ICall" Exp.sexp_of_t call
{ callee: Exp.t
; typ: Typ.t
; actuals: Exp.t iarray
; areturn: Reg.t option
; return: jump
; throw: jump option
; recursive: bool
; loc: Loc.t }]
| Return {exp; loc} -> | Return {exp; loc} ->
sexp_ctor "Return" [%sexp {exp: Exp.t option; loc: Loc.t}] sexp_ctor "Return" [%sexp {exp: Exp.t option; loc: Loc.t}]
| Throw {exc; loc} -> sexp_ctor "Throw" [%sexp {exc: Exp.t; loc: Loc.t}] | Throw {exc; loc} -> sexp_ctor "Throw" [%sexp {exc: Exp.t; loc: Loc.t}]
@ -281,6 +298,17 @@ let pp_jump fs {dst; retreating} =
(if retreating then "" else "") (if retreating then "" else "")
dst.lbl dst.lbl
let pp_call tag pp_callee fs
{callee; actuals; areturn; return; throw; recursive; loc; _} =
Format.fprintf fs
"@[<2>@[<7>%a%s @[<2>%s%a%a@]@]@ @[returnto %a%a;@]@]\t%a"
(Option.pp "%a := " Reg.pp)
areturn tag
(if recursive then "" else "")
pp_callee callee (pp_actuals Exp.pp) actuals pp_jump return
(Option.pp "@ throwto %a" pp_jump)
throw Loc.pp loc
let pp_term fs term = let pp_term fs term =
let pf pp = Format.fprintf fs pp in let pf pp = Format.fprintf fs pp in
let pp_goto fs jmp = Format.fprintf fs "goto %a;" pp_jump jmp in let pp_goto fs jmp = Format.fprintf fs "goto %a;" pp_jump jmp in
@ -301,14 +329,8 @@ let pp_term fs term =
(IArray.pp "@ " (fun fs jmp -> (IArray.pp "@ " (fun fs jmp ->
Format.fprintf fs "%s: %a" jmp.dst.lbl pp_goto jmp )) Format.fprintf fs "%s: %a" jmp.dst.lbl pp_goto jmp ))
tbl Loc.pp loc tbl Loc.pp loc
| Call {callee; actuals; areturn; return; throw; recursive; loc; _} -> | Call call -> pp_call "call" (fun fs f -> Function.pp fs f.name) fs call
pf "@[<2>@[<7>%acall @[<2>%s%a%a@]@]@ @[returnto %a%a;@]@]\t%a" | ICall call -> pp_call "icall" Exp.pp fs call
(Option.pp "%a := " Reg.pp)
areturn
(if recursive then "" else "")
Exp.pp callee (pp_actuals Exp.pp) actuals pp_jump return
(Option.pp "@ throwto %a" pp_jump)
throw Loc.pp loc
| Return {exp; loc} -> | Return {exp; loc} ->
pf "@[<2>return%a@]\t%a" (Option.pp " %a" Exp.pp) exp Loc.pp loc pf "@[<2>return%a@]\t%a" (Option.pp " %a" Exp.pp) exp Loc.pp loc
| Throw {exc; loc} -> pf "@[<2>throw %a@]\t%a" Exp.pp exc Loc.pp loc | Throw {exc; loc} -> pf "@[<2>throw %a@]\t%a" Exp.pp exc Loc.pp loc
@ -431,7 +453,8 @@ module Term = struct
let@ () = Invariant.invariant [%here] term [%sexp_of: t] in let@ () = Invariant.invariant [%here] term [%sexp_of: t] in
match term with match term with
| Switch _ | Iswitch _ -> assert true | Switch _ | Iswitch _ -> assert true
| Call {typ; actuals; areturn; _} -> ( | Call {typ; actuals; areturn; _} | ICall {typ; actuals; areturn; _}
-> (
match typ with match typ with
| Pointer {elt= Function {args; return= retn_typ; _}} -> | Pointer {elt= Function {args; return= retn_typ; _}} ->
assert (IArray.length args = IArray.length actuals) ; assert (IArray.length args = IArray.length actuals) ;
@ -460,8 +483,22 @@ module Term = struct
let iswitch ~ptr ~tbl ~loc = Iswitch {ptr; tbl; loc} |> check invariant let iswitch ~ptr ~tbl ~loc = Iswitch {ptr; tbl; loc} |> check invariant
let call ~callee ~typ ~actuals ~areturn ~return ~throw ~loc = let call ~name ~typ ~actuals ~areturn ~return ~throw ~loc =
Call let cal =
{ callee= {dummy_func with name= Function.mk typ name}
; typ
; actuals
; areturn
; return
; throw
; recursive= false
; loc }
in
let k = Call cal in
(k |> check invariant, fun ~callee -> cal.callee <- callee)
let icall ~callee ~typ ~actuals ~areturn ~return ~throw ~loc =
ICall
{callee; typ; actuals; areturn; return; throw; recursive= false; loc} {callee; typ; actuals; areturn; return; throw; recursive= false; loc}
|> check invariant |> check invariant
@ -473,6 +510,7 @@ module Term = struct
| Switch {loc; _} | Switch {loc; _}
|Iswitch {loc; _} |Iswitch {loc; _}
|Call {loc; _} |Call {loc; _}
|ICall {loc; _}
|Return {loc; _} |Return {loc; _}
|Throw {loc; _} -> |Throw {loc; _} ->
loc loc
@ -480,8 +518,9 @@ module Term = struct
let union_locals term vs = let union_locals term vs =
match term with match term with
| Call {areturn; _} -> Reg.Set.add_option areturn vs | Call {areturn; _} | ICall {areturn; _} ->
| _ -> vs Reg.Set.add_option areturn vs
| Switch _ | Iswitch _ | Return _ | Throw _ | Unreachable -> vs
end end
(** Basic-Blocks *) (** Basic-Blocks *)
@ -527,7 +566,6 @@ end
module BlockS = HashSet.Make (Block_label) module BlockS = HashSet.Make (Block_label)
module BlockQ = HashQueue.Make (Block_label) module BlockQ = HashQueue.Make (Block_label)
module FuncQ = HashQueue.Make (Function)
(** Functions *) (** Functions *)
@ -550,7 +588,8 @@ module Func = struct
let s = IArray.fold ~f:(fun (_, j) -> f j) tbl s in let s = IArray.fold ~f:(fun (_, j) -> f j) tbl s in
f els s f els s
| Iswitch {tbl; _} -> IArray.fold ~f tbl s | Iswitch {tbl; _} -> IArray.fold ~f tbl s
| Call {return; throw; _} -> Option.fold ~f throw (f return s) | Call {return; throw; _} | ICall {return; throw; _} ->
Option.fold ~f throw (f return s)
| Return _ | Throw _ | Unreachable -> s | Return _ | Throw _ | Unreachable -> s
in in
f blk s f blk s
@ -624,7 +663,7 @@ module Func = struct
IArray.iter tbl ~f:(fun (_, jmp) -> set_dst jmp) ; IArray.iter tbl ~f:(fun (_, jmp) -> set_dst jmp) ;
set_dst els set_dst els
| Iswitch {tbl; _} -> IArray.iter tbl ~f:set_dst | Iswitch {tbl; _} -> IArray.iter tbl ~f:set_dst
| Call {return; throw; _} -> | Call {return; throw; _} | ICall {return; throw; _} ->
set_dst return ; set_dst return ;
Option.iter throw ~f:set_dst Option.iter throw ~f:set_dst
| Return _ | Throw _ | Unreachable -> () | Return _ | Throw _ | Unreachable -> ()
@ -643,23 +682,22 @@ end
(** Derived meta-data *) (** Derived meta-data *)
module FuncQ = HashQueue.Make (Func)
let set_derived_metadata functions = let set_derived_metadata functions =
let compute_roots functions = let compute_roots functions =
let roots = FuncQ.create () in let roots = FuncQ.create () in
Function.Map.iter functions ~f:(fun func -> Function.Map.iter functions ~f:(fun func ->
FuncQ.enqueue_back_exn roots func.name func ) ; FuncQ.enqueue_back_exn roots func func ) ;
Function.Map.iter functions ~f:(fun func -> Function.Map.iter functions ~f:(fun func ->
Func.iter_term func ~f:(fun term -> Func.iter_term func ~f:(fun term ->
match term with match term with
| Call {callee; _} -> ( | Call {callee; _} ->
match Function.of_exp callee with FuncQ.remove roots callee |> (ignore : [> ] -> unit)
| Some callee ->
FuncQ.remove roots callee |> (ignore : [> ] -> unit)
| None -> () )
| _ -> () ) ) ; | _ -> () ) ) ;
roots roots
in in
let topsort functions roots = let topsort roots =
let tips_to_roots = BlockQ.create () in let tips_to_roots = BlockQ.create () in
let rec visit ancestors func src = let rec visit ancestors func src =
if BlockQ.mem tips_to_roots src then () if BlockQ.mem tips_to_roots src then ()
@ -675,18 +713,15 @@ let set_derived_metadata functions =
IArray.iter tbl ~f:(fun (_, jmp) -> jump jmp) ; IArray.iter tbl ~f:(fun (_, jmp) -> jump jmp) ;
jump els jump els
| Iswitch {tbl; _} -> IArray.iter tbl ~f:jump | Iswitch {tbl; _} -> IArray.iter tbl ~f:jump
| Call ({callee; return; throw; _} as call) -> | Call ({callee; return; throw; _} as cal) ->
( match if Block_label.Set.mem callee.entry ancestors then
let* name = Function.of_exp callee in cal.recursive <- true
Func.find name functions else visit ancestors func callee.entry ;
with jump return ;
| Some func -> Option.iter ~f:jump throw
if Block_label.Set.mem func.entry ancestors then | ICall ({return; throw; _} as call) ->
call.recursive <- true (* conservatively assume all indirect calls are recursive *)
else visit ancestors func func.entry call.recursive <- true ;
| None ->
(* conservatively assume all virtual calls are recursive *)
call.recursive <- true ) ;
jump return ; jump return ;
Option.iter ~f:jump throw Option.iter ~f:jump throw
| Return _ | Throw _ | Unreachable -> () ) ; | Return _ | Throw _ | Unreachable -> () ) ;
@ -707,7 +742,7 @@ let set_derived_metadata functions =
Function.Map.add_exn ~key:func.name ~data:func m ) Function.Map.add_exn ~key:func.name ~data:func m )
in in
let roots = compute_roots functions in let roots = compute_roots functions in
let tips_to_roots = topsort functions roots in let tips_to_roots = topsort roots in
set_sort_indices tips_to_roots ; set_sort_indices tips_to_roots ;
functions functions

@ -54,11 +54,11 @@ type cmnd = inst iarray
type label = string type label = string
(** A jump to a block. *) (** A jump to a block. *)
type jump = {mutable dst: block; mutable retreating: bool} type jump = private {mutable dst: block; mutable retreating: bool}
(** A call to a function. *) (** A call to a function. *)
and 'a call = and 'a call =
{ callee: 'a { mutable callee: 'a
; typ: Typ.t (** Type of the callee. *) ; typ: Typ.t (** Type of the callee. *)
; actuals: Exp.t iarray (** Actual arguments. *) ; actuals: Exp.t iarray (** Actual arguments. *)
; areturn: Reg.t option (** Register to receive return value. *) ; areturn: Reg.t option (** Register to receive return value. *)
@ -75,8 +75,8 @@ and term = private
[case] which is equal to [key], if any, otherwise invoke [els]. *) [case] which is equal to [key], if any, otherwise invoke [els]. *)
| Iswitch of {ptr: Exp.t; tbl: jump iarray; loc: Loc.t} | Iswitch of {ptr: Exp.t; tbl: jump iarray; loc: Loc.t}
(** Invoke the [jump] in [tbl] whose [dst] is equal to [ptr]. *) (** Invoke the [jump] in [tbl] whose [dst] is equal to [ptr]. *)
| Call of Exp.t call | Call of func call (** Call function with arguments. *)
(** Call function with arguments. A [global] for non-virtual call. *) | ICall of Exp.t call (** Indirect call function with arguments. *)
| Return of {exp: Exp.t option; loc: Loc.t} | Return of {exp: Exp.t option; loc: Loc.t}
(** Invoke [return] of the dynamically most recent [Call]. *) (** Invoke [return] of the dynamically most recent [Call]. *)
| Throw of {exc: Exp.t; loc: Loc.t} | Throw of {exc: Exp.t; loc: Loc.t}
@ -162,6 +162,16 @@ module Term : sig
val iswitch : ptr:Exp.t -> tbl:jump iarray -> loc:Loc.t -> term val iswitch : ptr:Exp.t -> tbl:jump iarray -> loc:Loc.t -> term
val call : val call :
name:string
-> typ:Typ.t
-> actuals:Exp.t iarray
-> areturn:Reg.t option
-> return:jump
-> throw:jump option
-> loc:Loc.t
-> t * (callee:func -> unit)
val icall :
callee:Exp.t callee:Exp.t
-> typ:Typ.t -> typ:Typ.t
-> actuals:Exp.t iarray -> actuals:Exp.t iarray

@ -14,7 +14,10 @@ let unknown_call call =
(fun fs call -> Llair.Loc.pp fs (Llair.Term.loc call)) (fun fs call -> Llair.Loc.pp fs (Llair.Term.loc call))
call call
(fun fs (call : Llair.Term.t) -> (fun fs (call : Llair.Term.t) ->
match call with Call {callee} -> Llair.Exp.pp fs callee | _ -> () ) match call with
| Call {callee} -> Llair.Function.pp fs callee.name
| ICall {callee} -> Llair.Exp.pp fs callee
| _ -> () )
call Llair.Term.pp call] call Llair.Term.pp call]
let invalid_access_count = ref 0 let invalid_access_count = ref 0

Loading…
Cancel
Save