|
|
|
(*
|
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
|
|
|
*)
|
|
|
|
|
|
|
|
(** Iterative Breadth-First Bounded Exploration
|
|
|
|
|
|
|
|
The analysis' semantics of control flow. *)
|
|
|
|
|
|
|
|
type exec_opts =
|
|
|
|
{ bound: int
|
|
|
|
; skip_throw: bool
|
|
|
|
; function_summaries: bool
|
|
|
|
; entry_points: Llair.Function.t list
|
|
|
|
; globals: Domain_used_globals.r }
|
|
|
|
|
|
|
|
module Make (Dom : Domain_intf.Dom) = struct
|
|
|
|
module Stack : sig
|
|
|
|
type t
|
|
|
|
type as_inlined_location = t [@@deriving compare, sexp_of]
|
|
|
|
|
|
|
|
val empty : t
|
|
|
|
|
|
|
|
val push_call :
|
|
|
|
Llair.func Llair.call -> bound:int -> Dom.from_call -> t -> t option
|
|
|
|
|
|
|
|
val pop_return : t -> (Dom.from_call * Llair.jump * t) option
|
|
|
|
|
|
|
|
val pop_throw :
|
|
|
|
t
|
|
|
|
-> 'a
|
|
|
|
-> unwind:
|
[sledge] Represent function formal parameters and actual arguments in order
Summary:
Previously, when LLAIR was in SSA form, blocks took parameters just
like functions, and it was sometimes necessary to partially apply a
block to some of the parameters. For example, blocks to which function
calls return would need to accept the return value as an argument, and
sometimes immediately jump to another block passing the rest of the
arguments as well. These "trampoline" blocks were partial applications
of the eventual block to all but the final, return value,
argument.
This partial application mechanism meant that function parameters and
arguments were represented as a stack, with the first argument at the
bottom, that is, in reverse order.
Now that LLAIR is free of SSA, this confusion is no longer needed, and
this diff changes the representation of function formal parameters and
actual arguments to be in the natural order. This also brings Call
instructions in line with Intrinsic instructions, which will make
changing the handling of intrinsics from Calls to Intrinsic less
error-prone.
Reviewed By: jvillard
Differential Revision: D25146163
fbshipit-source-id: d3ed07a45
4 years ago
|
|
|
( Llair.Reg.t iarray
|
|
|
|
-> Llair.Reg.Set.t
|
|
|
|
-> Dom.from_call
|
|
|
|
-> 'a
|
|
|
|
-> 'a)
|
|
|
|
-> (Dom.from_call * Llair.jump * t * 'a) option
|
|
|
|
end = struct
|
|
|
|
type t =
|
|
|
|
| Return of
|
|
|
|
{ recursive: bool (** return from a possibly-recursive call *)
|
|
|
|
; dst: Llair.Jump.t
|
[sledge] Represent function formal parameters and actual arguments in order
Summary:
Previously, when LLAIR was in SSA form, blocks took parameters just
like functions, and it was sometimes necessary to partially apply a
block to some of the parameters. For example, blocks to which function
calls return would need to accept the return value as an argument, and
sometimes immediately jump to another block passing the rest of the
arguments as well. These "trampoline" blocks were partial applications
of the eventual block to all but the final, return value,
argument.
This partial application mechanism meant that function parameters and
arguments were represented as a stack, with the first argument at the
bottom, that is, in reverse order.
Now that LLAIR is free of SSA, this confusion is no longer needed, and
this diff changes the representation of function formal parameters and
actual arguments to be in the natural order. This also brings Call
instructions in line with Intrinsic instructions, which will make
changing the handling of intrinsics from Calls to Intrinsic less
error-prone.
Reviewed By: jvillard
Differential Revision: D25146163
fbshipit-source-id: d3ed07a45
4 years ago
|
|
|
; formals: Llair.Reg.t iarray
|
|
|
|
; locals: Llair.Reg.Set.t
|
|
|
|
; from_call: Dom.from_call
|
|
|
|
; stk: t }
|
|
|
|
| Throw of Llair.Jump.t * t
|
|
|
|
| Empty
|
|
|
|
[@@deriving sexp_of]
|
|
|
|
|
|
|
|
type as_inlined_location = t [@@deriving sexp_of]
|
|
|
|
|
|
|
|
(* Treat a stack as a code location in a hypothetical expansion of the
|
|
|
|
program where all non-recursive functions have been completely
|
|
|
|
inlined. In particular, this means to compare stacks as if all Return
|
|
|
|
frames for recursive calls had been removed. Additionally, the
|
|
|
|
from_call info in Return frames is ignored. *)
|
|
|
|
let rec compare_as_inlined_location x y =
|
|
|
|
if x == y then 0
|
|
|
|
else
|
|
|
|
match (x, y) with
|
|
|
|
| Return {recursive= true; stk= x}, y
|
|
|
|
|x, Return {recursive= true; stk= y} ->
|
|
|
|
compare_as_inlined_location x y
|
|
|
|
| Return {dst= j; stk= x}, Return {dst= k; stk= y} -> (
|
|
|
|
match Llair.Jump.compare j k with
|
|
|
|
| 0 -> compare_as_inlined_location x y
|
|
|
|
| n -> n )
|
|
|
|
| Return _, _ -> -1
|
|
|
|
| _, Return _ -> 1
|
|
|
|
| Throw (j, x), Throw (k, y) -> (
|
|
|
|
match Llair.Jump.compare j k with
|
|
|
|
| 0 -> compare_as_inlined_location x y
|
|
|
|
| n -> n )
|
|
|
|
| Throw _, _ -> -1
|
|
|
|
| _, Throw _ -> 1
|
|
|
|
| Empty, Empty -> 0
|
|
|
|
|
|
|
|
let rec print_abbrev fs = function
|
|
|
|
| Return {recursive= false; stk= s} ->
|
|
|
|
print_abbrev fs s ;
|
|
|
|
Format.pp_print_char fs 'R'
|
|
|
|
| Return {recursive= true; stk= s} ->
|
|
|
|
print_abbrev fs s ;
|
|
|
|
Format.pp_print_string fs "R↑"
|
|
|
|
| Throw (_, s) ->
|
|
|
|
print_abbrev fs s ;
|
|
|
|
Format.pp_print_char fs 'T'
|
|
|
|
| Empty -> ()
|
|
|
|
|
|
|
|
let invariant s =
|
|
|
|
let@ () = Invariant.invariant [%here] s [%sexp_of: t] in
|
|
|
|
match s with
|
|
|
|
| Return _ | Throw (_, Return _) | Empty -> ()
|
|
|
|
| Throw _ -> fail "malformed stack: %a" print_abbrev s ()
|
|
|
|
|
|
|
|
let empty = Empty |> check invariant
|
|
|
|
|
|
|
|
let push_return Llair.{callee= {formals; locals}; return; recursive}
|
|
|
|
from_call stk =
|
|
|
|
Return {recursive; dst= return; formals; locals; from_call; stk}
|
|
|
|
|> check invariant
|
|
|
|
|
|
|
|
let push_throw jmp stk =
|
|
|
|
(match jmp with None -> stk | Some jmp -> Throw (jmp, stk))
|
|
|
|
|> check invariant
|
|
|
|
|
|
|
|
let push_call (Llair.{return; throw} as call) ~bound from_call stk =
|
|
|
|
[%Trace.call fun {pf} -> pf "%a" print_abbrev stk]
|
|
|
|
;
|
|
|
|
let rec count_f_in_stack acc f = function
|
|
|
|
| Return {stk= next_frame; dst= dest_block} ->
|
|
|
|
count_f_in_stack
|
|
|
|
(if Llair.Jump.equal dest_block f then acc + 1 else acc)
|
|
|
|
f next_frame
|
|
|
|
| _ -> acc
|
|
|
|
in
|
|
|
|
let n = count_f_in_stack 0 return stk in
|
|
|
|
( if n > bound then (
|
|
|
|
Report.hit_bound n ;
|
|
|
|
None )
|
|
|
|
else Some (push_throw throw (push_return call from_call stk)) )
|
|
|
|
|>
|
|
|
|
[%Trace.retn fun {pf} _ ->
|
|
|
|
pf "%d of %a on stack" n Llair.Jump.pp return]
|
|
|
|
|
|
|
|
let rec pop_return = function
|
|
|
|
| Throw (_, stk) -> pop_return stk
|
|
|
|
| Return {from_call; dst; stk} -> Some (from_call, dst, stk)
|
|
|
|
| Empty -> None
|
|
|
|
|
|
|
|
let pop_throw stk state ~unwind =
|
|
|
|
let rec pop_throw_ state = function
|
|
|
|
| Return {formals; locals; from_call; stk} ->
|
|
|
|
pop_throw_ (unwind formals locals from_call state) stk
|
|
|
|
| Throw (dst, Return {from_call; stk}) ->
|
|
|
|
Some (from_call, dst, stk, state)
|
|
|
|
| Empty -> None
|
|
|
|
| Throw _ as stk -> violates invariant stk
|
|
|
|
in
|
|
|
|
pop_throw_ state stk
|
|
|
|
end
|
|
|
|
|
|
|
|
module Work : sig
|
|
|
|
type t
|
|
|
|
|
|
|
|
val init : Dom.t -> Llair.block -> int -> t
|
|
|
|
|
|
|
|
type x
|
|
|
|
|
|
|
|
val skip : x
|
|
|
|
val seq : x -> x -> x
|
|
|
|
|
|
|
|
val add :
|
|
|
|
?prev:Llair.block
|
|
|
|
-> retreating:bool
|
|
|
|
-> Stack.t
|
|
|
|
-> Dom.t
|
|
|
|
-> Llair.block
|
|
|
|
-> x
|
|
|
|
|
|
|
|
val run : f:(Stack.t -> Dom.t -> Llair.block -> x) -> t -> unit
|
|
|
|
end = struct
|
|
|
|
module Edge = struct
|
|
|
|
module T = struct
|
|
|
|
type t =
|
|
|
|
{ dst: Llair.Block.t
|
|
|
|
; src: Llair.Block.t option
|
|
|
|
; stk: Stack.as_inlined_location }
|
|
|
|
[@@deriving compare, sexp_of]
|
|
|
|
end
|
|
|
|
|
|
|
|
include T
|
|
|
|
|
|
|
|
let pp fs {dst; src} =
|
|
|
|
Format.fprintf fs "#%i %%%s <--%a" dst.sort_index dst.lbl
|
|
|
|
(Option.pp "%a" (fun fs (src : Llair.Block.t) ->
|
|
|
|
Format.fprintf fs " #%i %%%s" src.sort_index src.lbl ))
|
|
|
|
src
|
|
|
|
end
|
|
|
|
|
|
|
|
module Depths = struct
|
|
|
|
module M = Map.Make (Edge)
|
|
|
|
|
|
|
|
type t = int M.t
|
|
|
|
|
|
|
|
let empty = M.empty
|
|
|
|
let find = M.find
|
|
|
|
let add = M.add
|
|
|
|
|
|
|
|
let join x y =
|
|
|
|
M.merge x y ~f:(fun _ -> function
|
|
|
|
| `Left d | `Right d -> Some d
|
|
|
|
| `Both (d1, d2) -> Some (Int.max d1 d2) )
|
|
|
|
end
|
|
|
|
|
|
|
|
type priority = int * Edge.t [@@deriving compare]
|
|
|
|
type priority_queue = priority FHeap.t
|
|
|
|
type waiting_states = (Dom.t * Depths.t) list Llair.Block.Map.t
|
|
|
|
type t = priority_queue * waiting_states * int
|
|
|
|
type x = Depths.t -> t -> t
|
|
|
|
|
|
|
|
let empty_waiting_states : waiting_states = Llair.Block.Map.empty
|
|
|
|
let pp_priority fs (n, e) = Format.fprintf fs "%i: %a" n Edge.pp e
|
|
|
|
|
|
|
|
let pp fs pq =
|
|
|
|
Format.fprintf fs "@[%a@]"
|
|
|
|
(List.pp " ::@ " pp_priority)
|
|
|
|
(FHeap.to_list pq)
|
|
|
|
|
|
|
|
let skip _ w = w
|
|
|
|
let seq x y d w = y d (x d w)
|
|
|
|
|
|
|
|
let add ?prev ~retreating stk state curr depths ((pq, ws, bound) as work)
|
|
|
|
=
|
|
|
|
let edge = {Edge.dst= curr; src= prev; stk} in
|
|
|
|
let depth = Option.value (Depths.find edge depths) ~default:0 in
|
|
|
|
let depth = if retreating then depth + 1 else depth in
|
|
|
|
if depth > bound then (
|
|
|
|
[%Trace.info "prune: %i: %a" depth Edge.pp edge] ;
|
|
|
|
Report.hit_bound bound ;
|
|
|
|
work )
|
|
|
|
else
|
|
|
|
let pq = FHeap.add pq (depth, edge) in
|
|
|
|
[%Trace.info "@[<6>enqueue %i: %a@ | %a@]" depth Edge.pp edge pp pq] ;
|
|
|
|
let depths = Depths.add ~key:edge ~data:depth depths in
|
|
|
|
let ws =
|
|
|
|
Llair.Block.Map.add_multi ~key:curr ~data:(state, depths) ws
|
|
|
|
in
|
|
|
|
(pq, ws, bound)
|
|
|
|
|
|
|
|
let init state curr bound =
|
|
|
|
add ~retreating:false Stack.empty state curr Depths.empty
|
|
|
|
(FHeap.create ~cmp:compare_priority, empty_waiting_states, bound)
|
|
|
|
|
|
|
|
let rec run ~f (pq0, ws, bnd) =
|
|
|
|
match FHeap.pop pq0 with
|
|
|
|
| Some ((_, ({Edge.dst; stk} as edge)), pq) -> (
|
|
|
|
match Llair.Block.Map.find_and_remove dst ws with
|
|
|
|
| Some (q :: qs, ws) ->
|
|
|
|
let join (qa, da) (q, d) = (Dom.join q qa, Depths.join d da) in
|
|
|
|
let skipped, (qs, depths) =
|
|
|
|
List.fold qs ([], q) ~f:(fun curr (skipped, joined) ->
|
|
|
|
match join curr joined with
|
|
|
|
| Some joined, depths -> (skipped, (joined, depths))
|
|
|
|
| None, _ -> (curr :: skipped, joined) )
|
|
|
|
in
|
|
|
|
let ws = Llair.Block.Map.add_exn ~key:dst ~data:skipped ws in
|
|
|
|
run ~f (f stk qs dst depths (pq, ws, bnd))
|
|
|
|
| _ ->
|
|
|
|
[%Trace.info "done: %a" Edge.pp edge] ;
|
|
|
|
run ~f (pq, ws, bnd) )
|
|
|
|
| None ->
|
|
|
|
[%Trace.info "queue empty"] ;
|
|
|
|
()
|
|
|
|
end
|
|
|
|
|
|
|
|
let exec_jump stk state block Llair.{dst; retreating} =
|
|
|
|
Work.add ~prev:block ~retreating stk state dst
|
|
|
|
|
|
|
|
let summary_table = Llair.Function.Tbl.create ()
|
|
|
|
|
|
|
|
let exec_call opts stk state block call globals =
|
|
|
|
let Llair.{callee; actuals; areturn; return; recursive} = call in
|
|
|
|
let Llair.{name; formals; freturn; locals; entry} = callee in
|
|
|
|
[%Trace.call fun {pf} ->
|
|
|
|
pf "%a from %a with state@ %a" Llair.Function.pp name
|
|
|
|
Llair.Function.pp return.dst.parent.name Dom.pp state]
|
|
|
|
;
|
|
|
|
let dnf_states =
|
|
|
|
if opts.function_summaries then Dom.dnf state else [state]
|
|
|
|
in
|
|
|
|
let domain_call =
|
|
|
|
Dom.call ~globals ~actuals ~areturn ~formals ~freturn ~locals
|
|
|
|
in
|
|
|
|
List.fold dnf_states Work.skip ~f:(fun state acc ->
|
|
|
|
match
|
|
|
|
if not opts.function_summaries then None
|
|
|
|
else
|
|
|
|
let maybe_summary_post =
|
|
|
|
let state = fst (domain_call ~summaries:false state) in
|
|
|
|
let* summary = Llair.Function.Tbl.find summary_table name in
|
|
|
|
List.find_map ~f:(Dom.apply_summary state) summary
|
|
|
|
in
|
|
|
|
[%Trace.info
|
|
|
|
"Maybe summary post: %a" (Option.pp "%a" Dom.pp)
|
|
|
|
maybe_summary_post] ;
|
|
|
|
maybe_summary_post
|
|
|
|
with
|
|
|
|
| None ->
|
|
|
|
let state, from_call =
|
|
|
|
domain_call ~summaries:opts.function_summaries state
|
|
|
|
in
|
|
|
|
Work.seq acc
|
|
|
|
( match
|
|
|
|
Stack.push_call call ~bound:opts.bound from_call stk
|
|
|
|
with
|
|
|
|
| Some stk ->
|
|
|
|
Work.add stk ~prev:block ~retreating:recursive state entry
|
|
|
|
| None -> (
|
|
|
|
match Dom.recursion_beyond_bound with
|
|
|
|
| `skip -> Work.seq acc (exec_jump stk state block return)
|
|
|
|
| `prune -> Work.skip ) )
|
|
|
|
| Some post -> Work.seq acc (exec_jump stk post block return) )
|
|
|
|
|>
|
|
|
|
[%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 () =
|
|
|
|
[%Trace.printf
|
|
|
|
"@[<v>%t@]" (fun fs ->
|
|
|
|
Llair.Function.Tbl.iteri summary_table ~f:(fun ~key ~data ->
|
|
|
|
Format.fprintf fs "@[<v>%a:@ @[%a@]@]@ " Llair.Function.pp key
|
|
|
|
(List.pp "@," Dom.pp_summary)
|
|
|
|
data ) )]
|
|
|
|
|
|
|
|
let exec_return ~opts stk pre_state (block : Llair.block) exp =
|
|
|
|
let Llair.{name; formals; freturn; locals} = block.parent in
|
|
|
|
[%Trace.call fun {pf} -> pf "from: %a" Llair.Function.pp name]
|
|
|
|
;
|
|
|
|
let summarize post_state =
|
|
|
|
if not opts.function_summaries then post_state
|
|
|
|
else
|
|
|
|
let function_summary, post_state =
|
|
|
|
Dom.create_summary ~locals ~formals post_state
|
|
|
|
in
|
|
|
|
Llair.Function.Tbl.add_multi ~key:name ~data:function_summary
|
|
|
|
summary_table ;
|
|
|
|
pp_st () ;
|
|
|
|
post_state
|
|
|
|
in
|
|
|
|
let exit_state =
|
|
|
|
match (freturn, exp) with
|
|
|
|
| Some freturn, Some return_val ->
|
|
|
|
Dom.exec_move (IArray.of_ (freturn, return_val)) pre_state
|
|
|
|
| None, None -> pre_state
|
|
|
|
| _ -> violates Llair.Func.invariant block.parent
|
|
|
|
in
|
|
|
|
( match Stack.pop_return stk with
|
|
|
|
| Some (from_call, retn_site, stk) ->
|
|
|
|
let post_state = summarize (Dom.post locals from_call exit_state) in
|
|
|
|
let retn_state = Dom.retn formals freturn from_call post_state in
|
|
|
|
exec_jump stk retn_state block retn_site
|
|
|
|
| None ->
|
|
|
|
(* Create and store a function summary for main *)
|
|
|
|
if
|
|
|
|
opts.function_summaries
|
|
|
|
&& List.exists ~f:(Llair.Function.equal name) opts.entry_points
|
|
|
|
then summarize exit_state |> (ignore : Dom.t -> unit) ;
|
|
|
|
Work.skip )
|
|
|
|
|>
|
|
|
|
[%Trace.retn fun {pf} _ -> pf ""]
|
|
|
|
|
|
|
|
let exec_throw stk pre_state (block : Llair.block) exc =
|
|
|
|
let func = block.parent in
|
|
|
|
[%Trace.call fun {pf} -> pf "from %a" Llair.Function.pp func.name]
|
|
|
|
;
|
|
|
|
let unwind formals scope from_call state =
|
|
|
|
Dom.retn formals (Some func.fthrow) from_call
|
|
|
|
(Dom.post scope from_call state)
|
|
|
|
in
|
|
|
|
( match Stack.pop_throw stk ~unwind pre_state with
|
|
|
|
| Some (from_call, retn_site, stk, unwind_state) ->
|
|
|
|
let fthrow = func.fthrow in
|
|
|
|
let exit_state =
|
|
|
|
Dom.exec_move (IArray.of_ (fthrow, exc)) unwind_state
|
|
|
|
in
|
|
|
|
let post_state = Dom.post func.locals from_call exit_state in
|
|
|
|
let retn_state =
|
|
|
|
Dom.retn func.formals func.freturn from_call post_state
|
|
|
|
in
|
|
|
|
exec_jump stk retn_state block retn_site
|
|
|
|
| None -> Work.skip )
|
|
|
|
|>
|
|
|
|
[%Trace.retn fun {pf} _ -> pf ""]
|
|
|
|
|
|
|
|
let exec_term :
|
|
|
|
exec_opts
|
|
|
|
-> Llair.program
|
|
|
|
-> Stack.t
|
|
|
|
-> Dom.t
|
|
|
|
-> Llair.block
|
|
|
|
-> Work.x =
|
|
|
|
fun opts pgm stk state block ->
|
|
|
|
[%Trace.info
|
|
|
|
"@[<2>exec term@\n@[%a@]@\n%a@]" Dom.pp state Llair.Term.pp block.term] ;
|
|
|
|
Report.step_term block.term ;
|
|
|
|
match block.term with
|
|
|
|
| Switch {key; tbl; els} ->
|
|
|
|
IArray.fold
|
|
|
|
~f:(fun (case, jump) x ->
|
|
|
|
match Dom.exec_assume state (Llair.Exp.eq key case) with
|
|
|
|
| Some state -> exec_jump stk state block jump |> Work.seq x
|
|
|
|
| None -> x )
|
|
|
|
tbl
|
|
|
|
( match
|
|
|
|
Dom.exec_assume state
|
|
|
|
(IArray.fold tbl Llair.Exp.true_ ~f:(fun (case, _) b ->
|
|
|
|
Llair.Exp.and_ (Llair.Exp.dq key case) b ))
|
|
|
|
with
|
|
|
|
| Some state -> exec_jump stk state block els
|
|
|
|
| None -> Work.skip )
|
|
|
|
| Iswitch {ptr; tbl} ->
|
|
|
|
IArray.fold tbl Work.skip ~f:(fun (jump : Llair.jump) x ->
|
|
|
|
match
|
|
|
|
Dom.exec_assume state
|
|
|
|
(Llair.Exp.eq ptr
|
|
|
|
(Llair.Exp.label
|
|
|
|
~parent:(Llair.Function.name jump.dst.parent.name)
|
|
|
|
~name:jump.dst.lbl))
|
|
|
|
with
|
|
|
|
| Some state -> exec_jump stk state block jump |> Work.seq x
|
|
|
|
| None -> x )
|
|
|
|
| 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 =
|
|
|
|
Option.to_list (Llair.Func.find name pgm.functions)
|
|
|
|
in
|
|
|
|
let callees, state = Dom.resolve_callee lookup callee state in
|
|
|
|
match callees with
|
|
|
|
| [] -> exec_skip_func stk state block areturn return
|
|
|
|
| callees ->
|
|
|
|
List.fold callees Work.skip ~f:(fun callee x ->
|
|
|
|
exec_call opts stk state block {call with callee}
|
|
|
|
(Domain_used_globals.by_function opts.globals callee.name)
|
|
|
|
|> Work.seq x ) )
|
|
|
|
| Return {exp} -> exec_return ~opts stk state block exp
|
|
|
|
| Throw {exc} ->
|
|
|
|
if opts.skip_throw then Work.skip
|
|
|
|
else exec_throw stk state block exc
|
|
|
|
| Unreachable -> Work.skip
|
|
|
|
|
|
|
|
let exec_inst : Llair.inst -> Dom.t -> (Dom.t, Dom.t * Llair.inst) result
|
|
|
|
=
|
|
|
|
fun inst state ->
|
|
|
|
[%Trace.info
|
|
|
|
"@[<2>exec inst@\n@[%a@]@\n%a@]" Dom.pp state Llair.Inst.pp inst] ;
|
|
|
|
Report.step_inst inst ;
|
|
|
|
Dom.exec_inst inst state
|
|
|
|
|> function
|
|
|
|
| Some state -> Result.Ok state | None -> Result.Error (state, inst)
|
|
|
|
|
|
|
|
let exec_block :
|
|
|
|
exec_opts
|
|
|
|
-> Llair.program
|
|
|
|
-> Stack.t
|
|
|
|
-> Dom.t
|
|
|
|
-> Llair.block
|
|
|
|
-> Work.x =
|
|
|
|
fun opts pgm stk state block ->
|
|
|
|
[%Trace.info "exec block %%%s" block.lbl] ;
|
|
|
|
match
|
|
|
|
Iter.fold_result ~f:exec_inst (IArray.to_iter block.cmnd) state
|
|
|
|
with
|
|
|
|
| Ok state -> exec_term opts pgm stk state block
|
|
|
|
| Error (state, inst) ->
|
|
|
|
Report.invalid_access_inst (Dom.report_fmt_thunk state) inst ;
|
|
|
|
Work.skip
|
|
|
|
|
|
|
|
let harness : exec_opts -> Llair.program -> (int -> Work.t) option =
|
|
|
|
fun opts pgm ->
|
|
|
|
List.find_map
|
|
|
|
~f:(fun entry_point -> Llair.Func.find entry_point pgm.functions)
|
|
|
|
opts.entry_points
|
|
|
|
|> function
|
[sledge] Represent function formal parameters and actual arguments in order
Summary:
Previously, when LLAIR was in SSA form, blocks took parameters just
like functions, and it was sometimes necessary to partially apply a
block to some of the parameters. For example, blocks to which function
calls return would need to accept the return value as an argument, and
sometimes immediately jump to another block passing the rest of the
arguments as well. These "trampoline" blocks were partial applications
of the eventual block to all but the final, return value,
argument.
This partial application mechanism meant that function parameters and
arguments were represented as a stack, with the first argument at the
bottom, that is, in reverse order.
Now that LLAIR is free of SSA, this confusion is no longer needed, and
this diff changes the representation of function formal parameters and
actual arguments to be in the natural order. This also brings Call
instructions in line with Intrinsic instructions, which will make
changing the handling of intrinsics from Calls to Intrinsic less
error-prone.
Reviewed By: jvillard
Differential Revision: D25146163
fbshipit-source-id: d3ed07a45
4 years ago
|
|
|
| Some {name; formals; freturn; locals; entry}
|
|
|
|
when IArray.is_empty formals ->
|
|
|
|
Some
|
|
|
|
(Work.init
|
|
|
|
(fst
|
|
|
|
(Dom.call ~summaries:opts.function_summaries
|
|
|
|
~globals:
|
|
|
|
(Domain_used_globals.by_function opts.globals name)
|
[sledge] Represent function formal parameters and actual arguments in order
Summary:
Previously, when LLAIR was in SSA form, blocks took parameters just
like functions, and it was sometimes necessary to partially apply a
block to some of the parameters. For example, blocks to which function
calls return would need to accept the return value as an argument, and
sometimes immediately jump to another block passing the rest of the
arguments as well. These "trampoline" blocks were partial applications
of the eventual block to all but the final, return value,
argument.
This partial application mechanism meant that function parameters and
arguments were represented as a stack, with the first argument at the
bottom, that is, in reverse order.
Now that LLAIR is free of SSA, this confusion is no longer needed, and
this diff changes the representation of function formal parameters and
actual arguments to be in the natural order. This also brings Call
instructions in line with Intrinsic instructions, which will make
changing the handling of intrinsics from Calls to Intrinsic less
error-prone.
Reviewed By: jvillard
Differential Revision: D25146163
fbshipit-source-id: d3ed07a45
4 years ago
|
|
|
~actuals:IArray.empty ~areturn:None ~formals:IArray.empty
|
|
|
|
~freturn ~locals (Dom.init pgm.globals)))
|
|
|
|
entry)
|
|
|
|
| _ -> None
|
|
|
|
|
|
|
|
let exec_pgm : exec_opts -> Llair.program -> unit =
|
|
|
|
fun opts pgm ->
|
|
|
|
match harness opts pgm with
|
|
|
|
| Some work -> Work.run ~f:(exec_block opts pgm) (work opts.bound)
|
|
|
|
| None -> fail "no applicable harness" ()
|
|
|
|
|
|
|
|
let compute_summaries opts pgm : Dom.summary list Llair.Function.Map.t =
|
|
|
|
assert opts.function_summaries ;
|
|
|
|
exec_pgm opts pgm ;
|
|
|
|
Llair.Function.Tbl.fold summary_table Llair.Function.Map.empty
|
|
|
|
~f:(fun ~key ~data map ->
|
|
|
|
match data with
|
|
|
|
| [] -> map
|
|
|
|
| _ -> Llair.Function.Map.add ~key ~data map )
|
|
|
|
end
|