1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
module M = St_memory
module B = St_bytecode
module C = St_class
module I = St_interp
type oop = M.oop
type frame = { ctx : oop; label : string; source : string; highlight : (int * int) option }
let dead (vm : I.vm) (ctx : oop) : bool = M.fetch (I.memory vm) ctx I.c_ip = M.nil
let label (vm : I.vm) (ctx : oop) : string =
let m = I.memory vm in
let home = I.context_home vm ctx in
let meth = M.fetch m home I.c_method in
let rcls = M.class_of m (M.fetch m home I.c_receiver) in
let mcls = B.method_class m meth in
let sel = M.string_of m (B.selector m meth) in
let base =
if mcls = rcls || mcls = M.nil then C.name m rcls ^ ">>" ^ sel else C.name m rcls ^ "(" ^ C.name m mcls ^ ")>>" ^ sel
in
if I.is_block_context vm ctx || I.is_closure_context vm ctx then "[] in " ^ base else base
let highlight (vm : I.vm) (ctx : oop) ~(next : bool) : (int * int) option =
let m = I.memory vm in
let ip = M.fetch m ctx I.c_ip in
if ip = M.nil then None
else
let ip = M.int_of ip in
let map = B.pcmap m (I.context_method vm ctx) in
let pick =
if next then List.find_opt (fun (pc, _, _) -> pc >= ip) map
else List.fold_left (fun acc ((pc, _, _) as e) -> if pc < ip then Some e else acc) None map
in
match pick with Some (_, a, b) when b > a -> Some (a, b) | _ -> None
let frames ?(stepping = false) (vm : I.vm) (p : I.process) : frame list =
let m = I.memory vm in
let rec go ctx top acc =
if ctx = M.nil || List.length acc > 200 then List.rev acc
else
let f =
{
ctx;
label = label vm ctx;
source = B.source m (I.context_method vm ctx);
highlight = highlight vm ctx ~next:(top && stepping);
}
in
go (M.fetch m ctx I.c_sender) false (f :: acc)
in
go p.top true []
let variables (vm : I.vm) (ctx : oop) : (string * oop) list =
let m = I.memory vm in
let home = I.context_home vm ctx in
let names = B.temp_names m (M.fetch m home I.c_method) in
let a = M.fields m home in
("self", a.(I.c_receiver))
:: List.mapi (fun i n -> (n, if I.c_temps + i < Array.length a then a.(I.c_temps + i) else M.nil)) names
|> List.filter (fun (n, _) -> n <> "" && n.[0] <> ' ')
let fields (vm : I.vm) (o : oop) : (string * oop) list =
let m = I.memory vm in
if M.is_int o then []
else
let k = M.known m in
let cls = M.class_of m o in
let names = C.inst_var_names m cls in
let limit = 200 in
match M.body m o with
| M.Pointers a ->
let n = List.length names in
List.mapi (fun i name -> (name, a.(i))) (List.filteri (fun i _ -> i < Array.length a) names)
@ List.init (min limit (max 0 (Array.length a - n))) (fun i -> (string_of_int (i + 1), a.(n + i)))
| M.Bytes b ->
let string = cls = k.string || cls = k.symbol in
List.init (min limit (Bytes.length b)) (fun i ->
let c = Char.code (Bytes.get b i) in
(string_of_int (i + 1), if string then k.characters.(c) else M.of_int c))
| M.Method (a, _) ->
Array.sub a 0 (Array.length a - B.trailer_size)
|> Array.to_list
|> List.mapi (fun i v -> ((if i = 0 then "header" else "literal " ^ string_of_int i), v))
| M.Float _ | M.Free -> []
let budget = 20_000_000
let run_until (vm : I.vm) (p : I.process) (cond : I.vm -> bool) : unit =
I.resume p;
I.run vm p ~budget ~stop_when:cond;
match p.state with I.Runnable -> I.suspend p "Step: still running" | _ -> ()
let over_condition (vm : I.vm) (p : I.process) (ctx : oop) : I.vm -> bool =
let m = I.memory vm in
let meth = I.context_method vm ctx in
let code = B.bytecodes m meth in
let pcs = List.map (fun (pc, _, _) -> pc) (B.pcmap m meth) in
let ip0 = match M.fetch m ctx I.c_ip with ip when M.is_int ip -> M.int_of ip | _ -> 0 in
let after =
if p.top <> ctx then Some ip0
else Option.map (fun s1 -> s1 + B.length_at code s1) (List.find_opt (fun pc -> pc >= ip0) pcs)
in
match after with
| None -> fun vm -> dead vm ctx
| Some after ->
let passed = ref false in
fun vm ->
if dead vm ctx then true
else if I.active_context vm = ctx then begin
let ip = I.ip vm in
if ip = after then passed := true;
!passed && List.mem ip pcs
end
else false
let step (vm : I.vm) (p : I.process) (ctx : oop) : unit = if not (dead vm ctx) then run_until vm p (over_condition vm p ctx)
let step_into (vm : I.vm) (p : I.process) (ctx : oop) : unit =
if not (dead vm ctx) then begin
let over = over_condition vm p ctx in
let m = I.memory vm in
run_until vm p (fun vm ->
let a = I.active_context vm in
(a <> ctx && M.fetch m a I.c_sender = ctx) || over vm)
end
let restart (vm : I.vm) (p : I.process) (ctx : oop) : bool =
let m = I.memory vm in
let ctx = I.context_home vm ctx in
if dead vm ctx then false
else begin
let old = M.fetch m ctx I.c_method in
let cls = B.method_class m old in
let meth = if cls = M.nil then old else Option.value (C.local_method m cls (B.selector m old)) ~default:old in
let h = B.header m meth in
let size = I.c_temps + h.frame_size in
if M.size m ctx < size then begin
let a = Array.make size M.nil in
Array.blit (M.fields m ctx) 0 a 0 (min size (M.size m ctx));
let bigger = M.alloc m ~cls:(M.known m).method_context (M.Pointers a) in
M.become m ctx bigger
end;
let a = M.fields m ctx in
a.(I.c_method) <- meth;
a.(I.c_ip) <- M.of_int 0;
a.(I.c_sp) <- M.of_int (I.c_temps + h.num_temps - 1);
for i = I.c_temps + h.num_args to Array.length a - 1 do
a.(i) <- M.nil
done;
p.top <- ctx;
I.flush_cache vm;
true
end
let proceed (p : I.process) : unit = I.resume p
let not_understood (vm : I.vm) (p : I.process) : (oop * string * oop) option =
let m = I.memory vm in
let top = p.top in
if top = M.nil || I.is_block_context vm top then None
else
let meth = M.fetch m top I.c_method in
if M.string_of m (B.selector m meth) <> "doesNotUnderstand:" then None
else
let msg = M.fetch m top I.c_temps in
let receiver = M.fetch m top I.c_receiver in
let sender = M.fetch m top I.c_sender in
if M.is_int msg || M.class_of m msg <> (M.known m).message then None
else Some (M.class_of m receiver, M.string_of m (M.fetch m msg 0), sender)
let template (selector : string) : string =
let n = St_ast.arity selector in
let pattern =
if n = 0 then selector
else if not (String.contains selector ':') then selector ^ " anObject"
else
String.split_on_char ':' selector |> List.filter (( <> ) "")
|> List.mapi (fun i k -> k ^ ": arg" ^ string_of_int (i + 1))
|> String.concat " "
in
pattern ^ "\n\t\"A method for " ^ selector ^ ", to write here.\"\n\t^self"