My working unpac repository
1(**************************************************************************)
2(* *)
3(* OCaml *)
4(* *)
5(* Xavier Leroy, projet Cambium, INRIA Paris *)
6(* *)
7(* Copyright 2023 Institut National de Recherche en Informatique et *)
8(* en Automatique. *)
9(* *)
10(* All rights reserved. This file is distributed under the terms of *)
11(* the GNU Lesser General Public License version 2.1, with the *)
12(* special exception on linking described in the file LICENSE. *)
13(* *)
14(**************************************************************************)
15
16(* Compute the parameters needed for allocating and managing stack frames
17 in the Emit phase. *)
18
19open Mach
20
21type analysis_result = {
22 contains_nontail_calls: bool;
23 frame_required: bool;
24 extra_stack_used: int;
25}
26
27class virtual stackframe_generic = object (self)
28
29(* Size of an exception handler block on the stack.
30 To be provided for each target. *)
31
32method virtual trap_handler_size : int
33
34(* Determine if an instruction performs a call that requires
35 the return address to be saved in the stack frame, and a stack frame to
36 be allocated.
37
38 At a minimum, these instructions include all non-tail calls,
39 both to OCaml functions or to C functions.
40
41 For exception-raising constructs, we get better stack backtraces
42 by treating them as non-tail calls, even if they are implemented as
43 tail calls.
44
45 This method can be overridden in [Stackframe] to implement target-specific
46 behaviors. *)
47
48method is_call = function
49 | Iop (Icall_ind | Icall_imm _ | Iextcall _) -> true
50 | Iop (Ialloc _) | Iop (Ipoll _) -> true
51 (* caml_alloc*, caml_garbage_collection (incl. polls) *)
52 | Iop (Iintop (Icheckbound) | Iintop_imm(Icheckbound, _)) -> !Clflags.debug
53 (* caml_ml_array_bound_error *)
54 | Iraise Lambda.Raise_notrace -> false
55 | Iraise (Lambda.Raise_regular | Lambda.Raise_reraise) -> true
56 (* caml_stash_backtrace; having a frame gives better stack backtrace *)
57 | Itrywith _ -> true
58 | _ -> false
59
60(* Determine if a function requires a stack frame to be allocated.
61 This is the case if it contains calls, but also if it allocates
62 variables on the stack.
63
64 This method can be overridden in [Stackframe] to implement target-specific
65 behaviors. *)
66
67method frame_required f contains_calls =
68 contains_calls ||
69 f.fun_num_stack_slots.(0) > 0 || f.fun_num_stack_slots.(1) > 0
70
71(* Analyze the body of a Mach function to determine
72 - whether it contains non-tail-calls to OCaml functions
73 - whether it requires allocating a stack frame and saving the return address
74 - how much extra stack space is needed for exception handlers
75 and for passing parameters to C function on stack.
76*)
77
78method analyze f =
79 let contains_nontail_calls = ref false
80 and contains_calls = ref false
81 and extra_space = ref 0 in
82 let rec analyze sp i =
83 if sp > !extra_space then extra_space := sp;
84 contains_calls := !contains_calls || self#is_call i.desc;
85 match i.desc with
86 | Iend -> ()
87 | Iop (Istackoffset delta) ->
88 analyze (sp + delta) i.next
89 | Iop (Itailcall_ind | Itailcall_imm _) -> ()
90 | Iop (Icall_ind | Icall_imm _) ->
91 contains_nontail_calls := true;
92 analyze sp i.next
93 | Iop _ ->
94 analyze sp i.next
95 | Ireturn -> ()
96 | Iifthenelse(_, ifso, ifnot) ->
97 analyze sp ifso; analyze sp ifnot; analyze sp i.next
98 | Iswitch(_, branches) ->
99 Array.iter (analyze sp) branches; analyze sp i.next
100 | Icatch(_, handlers, body) ->
101 List.iter (fun (_, handler) -> analyze sp handler) handlers;
102 analyze sp body;
103 analyze sp i.next
104 | Iexit _ -> ()
105 | Itrywith(body, handler) ->
106 analyze (sp + self#trap_handler_size) body;
107 analyze sp handler;
108 analyze sp i.next
109 | Iraise _ -> ()
110 in
111 analyze 0 f.fun_body;
112 { contains_nontail_calls = !contains_nontail_calls;
113 frame_required = self#frame_required f !contains_calls;
114 extra_stack_used = !extra_space }
115
116end