-
Notifications
You must be signed in to change notification settings - Fork 4
/
hotcaml.ml
737 lines (653 loc) · 21.7 KB
/
hotcaml.ml
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
let cwd = Sys.getcwd ()
let push_front xs x = xs := x :: !xs
let load_path = ref []
let packages = ref []
let entrypoints = ref []
let arg_specs = [
"-I", Arg.String (push_front load_path),
"<dir> Add <dir> to the list of include directories";
"-package", Arg.String (push_front packages),
"<pkg> Load findlib package <pkg>";
]
module Hotlink = struct
module type S = sig
val is_hot_loaded : unit -> bool
val is_hot_unloaded : unit -> bool
val on_unload : (unit -> unit) -> unit
val on_unload_or_at_exit : (unit -> unit) -> unit
end
let interface = "
val is_hot_loaded : unit -> bool
val is_hot_unloaded : unit -> bool
val on_unload : (unit -> unit) -> unit
val on_unload_or_at_exit : (unit -> unit) -> unit
"
type status = {
mutable unloaded: bool;
mutable on_unload : (unit -> unit) list;
}
let make () : status * (module S) =
let status = {
unloaded = false;
on_unload = [];
} in
(status, (module struct
let is_hot_loaded () = true
let is_hot_unloaded () = status.unloaded
let on_unload f =
if status.unloaded then
f ()
else
status.on_unload <- f :: status.on_unload
let on_unload_or_at_exit = on_unload
end))
let unload name s =
let fs = s.on_unload in
s.unloaded <- true;
s.on_unload <- [];
List.iter (fun f ->
try f ()
with exn ->
Printf.eprintf "Exception while unloading %s:\n%s\n"
name (Printexc.to_string exn)
) fs
end
let () =
Arg.parse arg_specs (push_front entrypoints)
(Printf.sprintf
"Usage: %s [-package findlib-package ...] source.ml ..."
Sys.argv.(0))
let entrypoints = List.rev !entrypoints
let add_dir =
let loaded = Hashtbl.create 7 in
fun dir ->
if not (Hashtbl.mem loaded dir) then (
Hashtbl.add loaded dir ();
Printf.ksprintf prerr_endline "Loading %S" dir;
Topdirs.dir_directory dir;
)
let () = push_front load_path cwd
let () = List.iter add_dir (List.rev !load_path)
let () =
let crc_intfs = Symtable.init_toplevel() in
Compmisc.init_path ();
Env.import_crcs ~source:Sys.executable_name crc_intfs;
Topfind.add_predicates ["byte"];
Topfind.don't_load_deeply ["compiler-libs.toplevel"];
()
let () =
(*let all = ref [] in
let _ = Symtable.filter_global_map (fun ident ->
all := ident :: !all;
true
) (Symtable.current_state ())
in*)
(*List.iter prerr_endline !Topfind.predicates;*)
begin try
Topfind.load_deeply (List.rev !packages);
with exn ->
Format.printf "%a\n%!" Location.report_exception exn
end;
(*let added = ref [] in
let _ =
Symtable.filter_global_map (fun ident ->
if not (List.mem ident !all) then
added := ident :: !added;
true
) (Symtable.current_state ())
in
List.iter (fun ident -> Format.printf "added %a\n" Ident.print ident)
!added*)
(* ** *)
module File_id : sig
type t
val missing : t
val identify : string -> t
val identify_option : string option -> t
val same : t -> t -> bool
end = struct
type t =
| Missing
| Found of Unix.stats
let missing = Missing
let identify path =
match Unix.stat path with
| exception Unix.Unix_error(Unix.ENOENT, "stat", _) -> Missing
| stats -> Found stats
let same t1 t2 =
match t1, t2 with
| Missing, Missing -> true
| Missing, Found _ | Found _, Missing -> false
| Found a, Found b ->
a.st_dev = b.st_dev &&
a.st_ino = b.st_ino &&
a.st_ctime = b.st_ctime &&
a.st_mtime = b.st_mtime &&
a.st_size = b.st_size
let identify_option = function
| None -> missing
| Some name -> identify name
end
module Hotpath : sig
val find : string -> string option
val changed : unit -> bool
end = struct
let path =
ref (List.map (fun path -> path, File_id.missing, [||]) !load_path)
let cache = Hashtbl.create 7
let changed () =
let changed = ref false in
path := List.map (fun (path, id, files) ->
(*Printf.eprintf "refresh path %S\n" path;*)
let id' = File_id.identify path in
if File_id.same id id' then
(path, id, files)
else (
changed := true;
let files = try Sys.readdir path with Sys_error _ -> [||] in
(path, id', files)
)
) !path;
if !changed then Hashtbl.clear cache;
!changed
let _ : bool = changed ()
let eq_mod_cap str1 str2 =
(*Printf.eprintf "eq_mod_cap %S %S\n%!" str1 str2;*)
str1 = str2 ||
let len = String.length str1 in
len = String.length str2 &&
(try
for i = 1 to len - 1 do
if str1.[i] <> str2.[i] then raise Exit
done;
true
with Exit -> false) &&
Char.lowercase_ascii str1.[0] = Char.lowercase_ascii str2.[0]
let find name =
(*Printf.eprintf "find: %S\n%!" name;*)
match Hashtbl.find cache name with
| found -> found
| exception Not_found ->
let exception Found of string in
let result =
List.find_map (fun (path, _, files) ->
(*Printf.eprintf "find: is %S in %S\n%!" name path;*)
try
Array.iter (fun name' ->
if eq_mod_cap name name' then raise (Found name')
) files;
None
with Found name -> Some (Filename.concat path name)
) !path
in
Hashtbl.add cache name result;
result
end
module Hotfinder : sig
type module_source = {
name: string;
interface: string option;
interface_id: File_id.t;
implementation: string option;
implementation_id: File_id.t;
}
val find : string -> module_source
end = struct
type module_source = {
name: string;
interface: string option;
interface_id: File_id.t;
implementation: string option;
implementation_id: File_id.t;
}
let find name =
if name = "" then invalid_arg "Finder.find: empty name";
let raw_module_name, raw_file_name =
if Filename.extension name = "" then
name, name ^ ".ml"
else
Filename.chop_extension name, name
in
let name =
String.capitalize_ascii (Filename.basename raw_module_name)
in
let raw_file_path =
if Sys.file_exists raw_file_name then
Some raw_file_name
else
match Hotpath.find raw_file_name with
| found -> found
| exception Not_found -> None
in
let implementation, interface =
match raw_file_path with
| None -> None, None
| Some path ->
let len = String.length path in
if path.[len - 1] = 'i' then
let ml_file = String.sub path 0 len in
let ml_file =
if Sys.file_exists ml_file
then Some ml_file
else None
in
ml_file, Some path
else
let mli_file = path ^ "i" in
let mli_file =
if Sys.file_exists mli_file
then Some mli_file
else None
in
Some path, mli_file
in
{
name; implementation; interface;
implementation_id = File_id.identify_option implementation;
interface_id = File_id.identify_option interface;
}
end
module Hotparser : sig
type implementation = (Parsetree.structure option, exn) result
val parse_implementation : string option -> implementation
type interface = (Parsetree.signature option, exn) result
val parse_interface : string option -> interface
val compose : string -> implementation -> interface -> implementation
end = struct
type implementation = (Parsetree.structure option, exn) result
let parse_implementation = function
| None -> Ok None
| Some path ->
match Pparse.parse_implementation ~tool_name:"hotcaml" path with
| exception exn -> Error exn
| tree -> Ok (Some tree)
type interface = (Parsetree.signature option, exn) result
let parse_interface = function
| None -> Ok None
| Some path ->
match Pparse.parse_interface ~tool_name:"hotcaml" path with
| exception exn -> Error exn
| tree -> Ok (Some tree)
let compose name implementation interface =
Result.bind implementation @@ fun implementation ->
Result.bind interface @@ fun interface ->
Result.ok @@
let open Ast_helper in
let mod_name = Location.mknoloc (Some name) in
let mod_expr = Option.map Mod.structure implementation in
let mod_type = Option.map Mty.signature interface in
match mod_expr, mod_type with
| None, None -> None
| Some impl, None ->
Some [Str.module_ (Mb.mk mod_name impl)]
| None, Some intf ->
let lid = Location.mknoloc (Longident.Lident name) in
let mod_expr = Mod.constraint_ (Mod.ident lid) intf in
Some [Str.rec_module [Mb.mk mod_name mod_expr]]
| Some impl, Some intf ->
Some [Str.module_ (Mb.mk mod_name (Mod.constraint_ impl intf))]
end
module String = Depend.String
module Hotdepend : sig
val dependencies : Parsetree.structure -> String.Set.t
end = struct
let bound = String.Map.singleton "Hotlink"
(Depend.Node (String.Set.empty, String.Map.empty))
let dependencies str =
Depend.free_structure_names := String.Set.empty;
Depend.add_implementation bound str;
!Depend.free_structure_names
end
module Hotresolver : sig
type program_unit = {
source: Hotfinder.module_source;
implementation: Hotparser.implementation;
interface: Hotparser.interface;
composed: Hotparser.implementation;
depends: String.Set.t;
}
type program = program_unit String.Map.t
val empty : program
val resolve : ?previous:program -> string list -> program
val changed : program -> bool
end = struct
type program_unit = {
source: Hotfinder.module_source;
implementation: Hotparser.implementation;
interface: Hotparser.interface;
composed: Hotparser.implementation;
depends: String.Set.t;
}
type program = program_unit String.Map.t
let update_unit previous name =
let previous = String.Map.find_opt name previous in
let source = Hotfinder.find name in
let implementation, impl_reused =
match previous with
| Some previous when
File_id.same
previous.source.implementation_id
source.implementation_id
->
previous.implementation, true
| Some _ | None ->
Hotparser.parse_implementation source.implementation, false
in
let interface, intf_reused =
match previous with
| Some previous when
File_id.same
previous.source.interface_id
source.interface_id
->
previous.interface, true
| Some _ | None ->
Hotparser.parse_interface source.interface, false
in
let composed, comp_reused = match previous with
| Some previous when
source.name = previous.source.name && impl_reused && intf_reused ->
previous.composed, true
| Some _ | None ->
Hotparser.compose source.name implementation interface, false
in
let depends =
match previous with
| Some previous when comp_reused -> previous.depends
| Some _ | None ->
match composed with
| Result.Ok (Some ast) -> Hotdepend.dependencies ast
| Result.Ok None -> String.Set.empty
| Result.Error _ ->
match previous with
| None -> String.Set.empty
| Some previous -> previous.depends
in
{ source; implementation; interface; composed; depends }
let empty = String.Map.empty
let resolve ?(previous=empty) entrypoints =
let rec load mod_name program =
if String.Map.mem mod_name program then program else
let p_unit = update_unit previous mod_name in
let program = String.Map.add mod_name p_unit program in
String.Set.fold load p_unit.depends program
in
List.fold_right load entrypoints empty
let changed (program: program) = not (
String.Map.for_all (fun _ {source; _} ->
File_id.same source.implementation_id
(File_id.identify_option source.implementation) &&
File_id.same source.interface_id
(File_id.identify_option source.interface)
) program
)
end
module Hotorder : sig
type t = string list
val order : Hotresolver.program -> string list -> (t, string) Result.t
end = struct
type t = string list
type ordered_set = string list * String.Set.t
let empty : ordered_set = ([], String.Set.empty)
let mem s (_, set : ordered_set ) = String.Set.mem s set
let add s set : ordered_set = if mem s set then set else
let (l, ss) = set in (s :: l, String.Set.add s ss)
exception Cycle of string * string list
let order program entrypoints =
let rec visit stack name order =
if mem name order then
order
else if mem name stack then
raise (Cycle (name, fst stack))
else
let stack = add name stack in
let p_unit = String.Map.find name program in
let order =
String.Set.fold (visit stack) p_unit.Hotresolver.depends order
in
add name order
in
try
let order, _index =
List.fold_left (fun order name -> visit empty name order)
empty entrypoints
in
Ok (List.rev order)
with Cycle (node, stack) ->
let rec extract = function
| [] -> assert false
| node' :: xs ->
if node = node'
then [node]
else node' :: extract xs
in
Error (
Printf.sprintf "Cyclic dependency found: %s"
(String.concat " -> " (node :: extract stack))
)
end
module Hottyper : sig
type program_unit =
| Present of {
depends: program_unit String.Map.t;
parsetree: Parsetree.structure;
typedtree_env: Env.t;
typedtree: Typedtree.structure;
signature: Types.signature;
}
| Absent
type program = (program_unit, exn) result String.Map.t
val empty : program
val typecheck :
?previous:program -> Hotresolver.program -> string list -> program
end = struct
type program_unit =
| Present of {
depends: program_unit String.Map.t;
parsetree: Parsetree.structure;
typedtree_env: Env.t;
typedtree: Typedtree.structure;
signature: Types.signature;
}
| Absent
type program = (program_unit, exn) result String.Map.t
let empty = String.Map.empty
let initial_env = Compmisc.initial_env ()
let hotlink =
let lexbuf = Lexing.from_string Hotlink.interface in
let sg = Parser.interface Lexer.token lexbuf in
(Typemod.transl_signature initial_env sg).sig_type
let hotlink_ident = Ident.create_local "Hotlink"
let add_hotlink env =
Env.add_module hotlink_ident Mp_present (Types.Mty_signature hotlink) env
let just_typecheck env pstr =
Typecore.reset_delayed_checks ();
let (str, sg, sn, _, newenv) = Typemod.type_toplevel_phrase env pstr in
let sg' = Typemod.Signature_names.simplify newenv sn sg in
ignore (Includemod.signatures ~mark:Mark_positive env sg sg');
Typecore.force_delayed_checks ();
(env, str, sg)
let typecheck_with_depends depends pstr =
let env = add_hotlink initial_env in
let env =
String.Map.fold (fun _ dep env ->
match dep with
| Absent -> env
| Present t-> Env.add_signature t.signature env
) depends env
in
just_typecheck env pstr
let typecheck_module previous source (program : program) mod_name =
let s_unit = String.Map.find mod_name source in
match s_unit.Hotresolver.composed with
| Error _ -> None
| Ok None -> Some (Ok Absent)
| Ok (Some parsetree) ->
match
String.Set.fold (fun mod_name acc ->
match String.Map.find mod_name program with
| Ok t_unit -> String.Map.add mod_name t_unit acc
| Error _ -> raise Not_found
) s_unit.Hotresolver.depends String.Map.empty
with
| exception Not_found -> None
| depends ->
Option.some @@
match String.Map.find_opt mod_name previous with
| Some (Ok (Present previous) as result) when
previous.parsetree == parsetree &&
String.Map.equal (==) previous.depends depends ->
result
| Some _ | None ->
match typecheck_with_depends depends parsetree with
| exception exn -> Error exn
| (typedtree_env, typedtree, signature) ->
Ok (Present { depends; parsetree;
typedtree_env; typedtree; signature;
})
let typecheck ?(previous=empty) source modules =
List.fold_left (fun program mod_name ->
match typecheck_module previous source program mod_name with
| None -> program
| Some t_unit -> String.Map.add mod_name t_unit program
) empty modules
end
module Hoterrors : sig
val validate_parse : Hotresolver.program -> Hotorder.t -> (unit, string) result
val validate_types : Hottyper.program -> Hotorder.t -> (unit, string) result
end = struct
let validate_parse program order =
let first_error mod_name =
let {Hotresolver. implementation; interface; _} =
String.Map.find mod_name program
in
match implementation, interface with
| (Result.Error exn, _) | (_, Result.Error exn) ->
Some (Format.asprintf "%a" Location.report_exception exn)
| _ -> None
in
match List.find_map first_error order with
| None -> Ok ()
| Some msg -> Error msg
let validate_types (program: Hottyper.program) order =
let first_error mod_name =
match String.Map.find mod_name program with
| Result.Ok _ -> None
| Result.Error exn ->
Some (Format.asprintf "%a" Location.report_exception exn)
in
match List.find_map first_error order with
| None -> Ok ()
| Some msg -> Error msg
end
module Hotloader : sig
type program
val empty : program
val load : ?previous:program -> Hottyper.program -> Hotorder.t ->
program * (unit, string) result
end = struct
type link_map = (Hottyper.program_unit * Hotlink.status option) String.Map.t
type program = (string * (Hottyper.program_unit * Hotlink.status option)) list
let empty = []
let unload_previous (program : Hottyper.program) (previous : program)
: link_map
=
let still_loaded =
List.fold_left (fun acc (name, (prev, link)) ->
let need_unload =
match String.Map.find_opt name program with
| Some p_unit -> Result.get_ok p_unit != prev
| None -> true
in
if need_unload then (
begin match link with
| None -> ()
| Some link ->
prerr_endline ("Unloading " ^ name);
Hotlink.unload name link;
end;
acc
) else
String.Map.add name (prev, link) acc
) String.Map.empty previous
in
still_loaded
exception Failed of link_map * string
let load_new loaded program order =
let load_unit loaded name =
if String.Map.mem name loaded then
loaded
else
let p_unit = Result.get_ok (String.Map.find name program) in
match p_unit with
| Hottyper.Absent -> String.Map.add name (p_unit, None) loaded
| Hottyper.Present t ->
let status, link = Hotlink.make () in
try
let lam = Translmod.transl_toplevel_definition t.typedtree in
let slam = Simplif.simplify_lambda lam in
let init_code, fun_code = Bytegen.compile_phrase slam in
Toploop.setvalue "Hotlink" (Obj.repr link);
let (code, reloc, events) =
Emitcode.to_memory init_code fun_code
in
Symtable.patch_object code reloc;
Symtable.check_global_initialized reloc;
Symtable.update_global_table();
(*let initial_bindings = !toplevel_value_bindings in*)
let _bytecode, closure = Meta.reify_bytecode code [| events |] None in
ignore (closure () : Obj.t);
prerr_endline ("Loaded " ^ name);
String.Map.add name (p_unit, Some status) loaded
with exn ->
let message =
Format.asprintf "%s compilation failed: %a\n%!"
name Location.report_exception exn
in
Hotlink.unload name status;
raise (Failed (loaded, message))
in
let get_program loaded order =
List.map (fun name ->
name,
match String.Map.find_opt name loaded with
| None -> Hottyper.Absent, None
| Some (p_unit, link) -> p_unit, link
) order
in
let loaded, result =
match List.fold_left load_unit loaded order with
| loaded -> loaded, Ok ()
| exception (Failed (loaded, msg)) -> loaded, Error msg
in
(get_program loaded order, result)
let load ?(previous=(empty : program)) (program : Hottyper.program) order =
let still_loaded = unload_previous program previous in
load_new still_loaded program order
end
let quit = ref false
let () =
Sys.set_signal Sys.sigint (Sys.Signal_handle (fun _ -> quit := true))
let () =
let source = ref Hotresolver.empty in
let typed = ref Hottyper.empty in
let loaded = ref Hotloader.empty in
let process () =
source := Hotresolver.resolve ~previous:!source entrypoints;
Result.bind (Hotorder.order !source entrypoints) @@ fun order ->
Result.bind (Hoterrors.validate_parse !source order) @@ fun () ->
typed := Hottyper.typecheck ~previous:!typed !source order;
Result.bind (Hoterrors.validate_types !typed order) @@ fun () ->
let loaded', result = Hotloader.load ~previous:!loaded !typed order in
loaded := loaded';
result
in
while true do
begin match process () with
| Ok () -> ()
| Error msg -> prerr_endline msg
end;
while not (Hotpath.changed () || Hotresolver.changed !source) do
Unix.sleepf 0.05
done;
done