2 * Copyright (c) 2017, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
12 module SourceText
= Full_fidelity_source_text
14 Full_fidelity_syntax_tree.WithSyntax
(Full_fidelity_positioned_syntax
)
15 module Logger
= HackcEventLogger
17 (*****************************************************************************)
18 (* Types, constants *)
19 (*****************************************************************************)
24 let is_mode_cli = function
28 let is_mode_daemon = function
35 config_list
: string list
;
37 output_file
: string option;
38 config_file
: string option;
40 input_file_list
: string option;
41 dump_symbol_refs
: bool;
45 for_debugger_eval
: bool;
46 (* below are used during Rust porting *)
47 disable_toplevel_elaboration
: bool;
50 dump_desugared_expression_trees
: bool;
53 type message_handler
= Hh_json.json
-> string -> unit
55 type message_handlers
= {
56 set_config
: message_handler
;
57 compile
: message_handler
;
58 facts
: message_handler
;
59 parse
: message_handler
;
60 error
: message_handler
;
63 (*****************************************************************************)
65 (*****************************************************************************)
70 printing_t
: float ref;
73 let new_debug_time () =
74 { parsing_t
= ref 0.0; codegen_t
= ref 0.0; printing_t
= ref 0.0 }
76 let prev_vm_hwm = ref 0
78 let log_peak_mem file action
=
79 match Memory_stats.get_vm_hwm
() with
80 | Some vm_hwm
when vm_hwm
> !prev_vm_hwm ->
81 prev_vm_hwm := vm_hwm
;
83 (Option.value file ~default
:"")
84 (Memory_stats.get_vm_rss
() |> Option.value ~default
:0)
89 (*****************************************************************************)
91 (*****************************************************************************)
97 let is_file_path_for_evaled_code s
=
98 let s = Relative_path.to_absolute
s in
99 String_utils.string_ends_with
s ") : eval()'d code"
101 let print_compiler_version () =
103 let compiler_version_msg =
107 ("type", JSON_String
"compiler_version");
108 ("version", JSON_String
(Compiler_id.get_compiler_id
()));
111 P.printf
"%s\n%!" compiler_version_msg)
113 let assert_regular_file filename
=
115 (not
(Sys.file_exists filename
))
116 || Poly.((Unix.stat filename
).Unix.st_kind
<> Unix.S_REG
)
118 raise
(Arg.Bad
(filename ^
" not a valid file"))
120 let parse_options () =
121 let fn_ref = ref None
in
122 let want_version = ref false in
123 let fallback = ref false in
124 let debug_time = ref false in
125 let config_list = ref [] in
126 let mode = ref CLI
in
127 let output_file = ref None
in
128 let config_file = ref None
in
129 let input_file_list = ref None
in
130 let dump_symbol_refs = ref false in
131 let extract_facts = ref false in
132 let dump_config = ref false in
133 let log_stats = ref false in
134 let for_debugger_eval = ref false in
135 let disable_toplevel_elaboration = ref false in
136 let include_header = ref false in
137 let dump_desugared_expression_trees = ref false in
139 P.sprintf
"Usage: hh_single_compile (%s) filename\n" Sys.argv
.(0)
143 ("--version", Arg.Set
want_version, " print the version and do nothing");
144 ("--fallback", Arg.Set
fallback, " Enables fallback compilation");
147 " Enables debugging logging for elapsed time" );
148 ("--facts", Arg.Set
extract_facts, "Extract facts from the source code.");
150 Arg.String
(fun str
-> config_list := str
:: !config_list),
151 " Configuration: Server.Port=<value> "
153 ^
"\t\tAllows overriding config options passed on a file" );
157 assert_regular_file str
;
158 config_file := Some str
),
159 " Config file in JSON format" );
161 Arg.String
(fun str
-> output_file := Some str
),
162 " Output file. Creates it if necessary" );
164 Arg.Unit
(fun () -> mode := DAEMON
),
165 " Run a daemon which processes Hack source from standard input" );
166 ( "--input-file-list",
167 Arg.String
(fun str
-> input_file_list := Some str
),
168 " read a list of files (one per line) from the file `input-file-list'"
170 ( "--dump-symbol-refs",
171 Arg.Set
dump_symbol_refs,
172 " Dump symbol ref sections of HHAS" );
173 ("--dump-config", Arg.Set
dump_config, " Dump configuration settings");
174 ( "--enable-logging-stats",
175 Arg.Unit
(fun () -> log_stats := true),
176 " Starts logging stats" );
177 ( "--stop-logging-stats",
178 Arg.Unit
(fun () -> log_stats := false),
179 " Stop logging stats" );
180 ( "--for-debugger-eval",
181 Arg.Unit
(fun () -> for_debugger_eval := true),
182 " Mutate the program as if we're in the debugger repl" );
183 ( "--disable-toplevel-elaboration",
184 Arg.Unit
(fun () -> disable_toplevel_elaboration := true),
185 "Disable toplevel definition elaboration" );
186 ( "--include-header",
187 Arg.Unit
(fun () -> include_header := true),
188 "Include JSON header" );
189 ( "--dump-desugared-expression-trees",
190 Arg.Unit
(fun () -> dump_desugared_expression_trees := true),
191 "Print the source code with expression tree literals desugared. Best effort debugging tool."
195 let options = Arg.align ~limit
:25 options in
196 Arg.parse
options (fun fn
-> fn_ref := Some fn
) usage;
197 if !want_version then (
198 print_compiler_version ();
201 if is_mode_daemon !mode then print_compiler_version ();
202 let needs_file = Option.is_none
!input_file_list in
207 if is_mode_cli !mode then
212 if is_mode_cli !mode then
221 fallback = !fallback;
222 config_list = !config_list;
223 debug_time = !debug_time;
224 output_file = !output_file;
225 config_file = !config_file;
227 input_file_list = !input_file_list;
228 dump_symbol_refs = !dump_symbol_refs;
229 dump_config = !dump_config;
230 log_stats = !log_stats;
231 extract_facts = !extract_facts;
232 for_debugger_eval = !for_debugger_eval;
233 disable_toplevel_elaboration = !disable_toplevel_elaboration;
234 include_header = !include_header;
235 dump_desugared_expression_trees = !dump_desugared_expression_trees;
238 let fail_daemon file error
=
240 let file = Option.value ~default
:"[unknown]" file in
245 ("type", JSON_String
"error");
246 ("file", JSON_String
file);
247 ("error", JSON_String error
);
250 P.printf
"%s\n%!" msg;
253 let rec dispatch_loop handlers
=
256 let read_message () =
257 let line = Caml.read_line
() in
258 let header = json_of_string
line in
259 let file = get_field_opt
(get_string
"file") header in
260 let bytes = get_field
(get_number_int
"bytes") (fun _af
-> 0) header in
261 let body = Bytes.create
bytes in
263 Caml.really_input
Caml.stdin
body 0 bytes;
264 (header, Bytes.to_string
body, file)
268 ("Exception reading message body: " ^
Caml.Printexc.to_string exc
)
270 let (header, body, file) = read_message () in
275 fail_daemon None
("Cannot determine type of message: " ^ af
))
278 let log_hackc_mem_stats =
279 get_field_opt
(get_bool
"log_hackc_mem_stats") header
280 |> Option.value ~default
:false
283 | "code" -> handlers
.compile
header body
284 | "error" -> handlers
.error
header body
285 | "config" -> handlers
.set_config
header body
286 | "facts" -> handlers
.facts
header body
287 | "parse" -> handlers
.parse
header body
288 | _
-> fail_daemon None
("Unhandled message type '" ^
msg_type ^
"'"));
289 (try if log_hackc_mem_stats then log_peak_mem file msg_type
291 let stack = Caml.Printexc.get_backtrace
() in
292 Printf.eprintf
"%s\n" stack;
295 ( "Exception reading message body: "
296 ^
Caml.Printexc.to_string exc
298 dispatch_loop handlers
))
300 let print_debug_time_info filename
debug_time =
301 let stat = Caml.Gc.stat () in
302 P.eprintf
"File %s:\n" (Relative_path.to_absolute filename
);
303 P.eprintf
"Parsing: %0.3f s\n" !(debug_time.parsing_t
);
304 P.eprintf
"Codegen: %0.3f s\n" !(debug_time.codegen_t
);
305 P.eprintf
"Printing: %0.3f s\n" !(debug_time.printing_t
);
306 P.eprintf
"MinorWords: %0.3f\n" stat.Caml.Gc.minor_words
;
307 P.eprintf
"PromotedWords: %0.3f\n" stat.Caml.Gc.promoted_words
309 let mode_to_string = function
313 let log_success compiler_options filename
debug_time =
315 ~filename
:(Relative_path.to_absolute filename
)
316 ~parsing_t
:!(debug_time.parsing_t
)
317 ~codegen_t
:!(debug_time.codegen_t
)
318 ~printing_t
:!(debug_time.printing_t
)
319 ~
mode:(mode_to_string compiler_options
.mode)
321 let log_fail compiler_options filename exc ~
stack =
323 ~filename
:(Relative_path.to_absolute filename
)
324 ~
mode:(mode_to_string compiler_options
.mode)
325 ~exc
:(Caml.Printexc.to_string exc ^
"\n" ^
stack)
328 (bytecode
: string list
)
329 (config
: Compile_ffi.rust_output_config
)
330 (file : Relative_path.t
)
331 (debug_time : debug_time option)
332 (log_config_json
: bool)
333 (config_jsons
: string list
)
334 (config_list : string list
) =
336 match config
.Compile_ffi.output_file with
337 | Some
file -> Sys_utils.write_strings_to_file ~
file
340 List.iter ~f
:(P.printf
"%s") c
;
343 if config
.Compile_ffi.include_header then (
345 List.fold ~f
:(fun len
s -> len
+ String.length
s) ~init
:0 bytecode
347 let abs_path = Relative_path.to_absolute
file in
351 ("bytes", int_
bytes);
352 ("file", JSON_String
abs_path);
353 ("type", JSON_String
"success");
360 (Relative_path.to_absolute
file)
366 ~f
:(fun x
-> Hh_json.JSON_String x
)
367 (config_jsons
@ config_list)) )
373 match debug_time with
375 let json_microsec t
=
376 Hh_json.int_
@@ int_of_float
@@ (t
*. 1000000.0)
378 ("parsing_time", json_microsec !(debug_time.parsing_t
))
379 :: ("codegen_time", json_microsec !(debug_time.codegen_t
))
380 :: ("printing_time", json_microsec !(debug_time.printing_t
))
384 write Hh_json.[json_to_string
@@ JSON_Object
msg];
392 (compiler_options
: options)
396 (* dummy line to load Full_fidelity_ast *)
397 let _ = Full_fidelity_ast.make_env filename
in
401 re_filepath
= filename
;
402 re_config_jsons
= List.rev config_jsons
;
403 re_config_list
= compiler_options
.config_list;
405 ( ( if is_systemlib
then
409 lor ( if is_file_path_for_evaled_code filename
then
413 lor ( if compiler_options
.for_debugger_eval then
417 lor ( if compiler_options
.dump_symbol_refs then
422 if compiler_options
.disable_toplevel_elaboration then
428 match Compile_ffi.rust_from_text_ffi
env rust_output_config source_text
with
430 | Error
msg -> raise
(Failure
msg)
432 let extract_facts ~compiler_options ~config_jsons ~filename text
=
433 let (co
, log_config_json
) =
434 Hhbc_options.apply_config_overrides_statelessly
435 compiler_options
.config_list
440 Facts_parser.extract_as_json_string
441 ~php5_compat_mode
:true
442 ~hhvm_compat_mode
:true
443 ~disable_nontoplevel_declarations
:
444 (phpism_disable_nontoplevel_declarations co
)
445 ~disable_legacy_soft_typehints
:(disable_legacy_soft_typehints co
)
446 ~allow_new_attribute_syntax
:(allow_new_attribute_syntax co
)
447 ~disable_legacy_attribute_syntax
:(disable_legacy_attribute_syntax co
)
448 ~enable_xhp_class_modifier
:(enable_xhp_class_modifier co
)
449 ~disable_xhp_element_mangling
:(disable_xhp_element_mangling co
)
450 ~disallow_hash_comments
:(disallow_hash_comments co
)
453 |> Option.value ~default
:"");
458 let parse_hh_file ~config_jsons ~compiler_options filename
body =
459 let (co
, log_config_json
) =
460 Hhbc_options.apply_config_overrides_statelessly
461 compiler_options
.config_list
465 let file = Relative_path.create
Relative_path.Dummy filename
in
466 let source_text = SourceText.make
file body in
467 let mode = Full_fidelity_parser.parse_mode
source_text in
469 Full_fidelity_parser_env.make
471 ~php5_compat_mode
:true
472 ~hhvm_compat_mode
:true
473 ~disable_nontoplevel_declarations
:
474 (phpism_disable_nontoplevel_declarations co
)
475 ~disable_legacy_soft_typehints
:(disable_legacy_soft_typehints co
)
476 ~allow_new_attribute_syntax
:(allow_new_attribute_syntax co
)
477 ~disable_legacy_attribute_syntax
:(disable_legacy_attribute_syntax co
)
478 ~enable_xhp_class_modifier
:(enable_xhp_class_modifier co
)
479 ~disable_xhp_element_mangling
:(disable_xhp_element_mangling co
)
480 ~disallow_hash_comments
:(disallow_hash_comments co
)
481 ~disallow_fun_and_cls_meth_pseudo_funcs
:
482 (disallow_fun_and_cls_meth_pseudo_funcs co
)
483 ~disallow_inst_meth
:(disallow_inst_meth co
)
487 let syntax_tree = SyntaxTree.make ~
env source_text in
488 let json = SyntaxTree.to_json
syntax_tree in
489 ([Hh_json.json_to_string
json], co
, log_config_json
))
491 (*****************************************************************************)
492 (* Main entry point *)
493 (*****************************************************************************)
495 let process_single_source_unit
504 if compiler_options
.extract_facts then
505 let (output
, _, log_config_json
) =
506 extract_facts ~compiler_options ~config_jsons ~filename
source_text
515 compiler_options
.config_list
523 (Full_fidelity_source_text.make filename
source_text)
525 let stack = Caml.Printexc.get_backtrace
() in
526 if compiler_options
.log_stats then
527 log_fail compiler_options filename exc ~
stack;
528 handle_exception filename exc
530 let desugar_and_print_expr_trees
531 ~config_jsons ~compiler_options
(filename
: string) : unit =
532 let rel_path = Relative_path.create
Relative_path.Dummy filename
in
536 re_filepath
= rel_path;
537 re_config_jsons
= List.rev config_jsons
;
538 re_config_list
= compiler_options
.config_list;
540 ( ( if is_file_path_for_evaled_code rel_path then
544 lor ( if compiler_options
.for_debugger_eval then
548 lor ( if compiler_options
.dump_symbol_refs then
553 if compiler_options
.disable_toplevel_elaboration then
559 Compile_ffi.desugar_and_print_expr_trees env
561 let decl_and_run_mode compiler_options
=
564 (* list of pending config JSONs *)
565 let config_jsons = ref [] in
566 let add_config (config_json
: string option) =
567 config_jsons := config_json
:: !config_jsons
571 match !config_jsons with
572 | _ :: old_config_jsons
-> old_config_jsons
575 let get_config_jsons () = List.filter_map ~f
:(fun x
-> x
) !config_jsons in
576 let ini_config_json : string option =
578 ~f
:(fun path
-> (try Sys_utils.cat path
with _ -> ""))
579 compiler_options
.config_file
581 add_config ini_config_json;
582 Ident.track_names
:= true;
584 match compiler_options
.mode with
586 let add_config_overrides header =
587 let config_overrides =
589 (get_obj
"config_overrides")
590 (fun _af
-> JSON_Object
[])
592 |> Hh_json.json_to_string
594 add_config (Some
config_overrides)
596 let get_filename_and_path (header : json) =
603 ("Cannot determine file name of source unit: " ^ af
))
606 (filename, Relative_path.create
Relative_path.Dummy
filename)
608 let body_or_file_contents (body : string) (filename : string) =
609 (* if body is empty - read file from disk *)
610 if String.length
body = 0 then
611 Sys_utils.cat
filename
624 Compile_ffi.{ include_header = true; output_file = None
}
626 ( if Hhbc_options.log_extern_compiler_perf hhbc_options
then
631 (get_config_jsons ())
632 compiler_options
.config_list
634 let handle_exception filename exc
=
635 let abs_path = Relative_path.to_absolute
filename in
640 ("type", JSON_String
"error");
641 ("file", JSON_String
abs_path);
642 ("error", JSON_String
(Caml.Printexc.to_string exc
));
645 P.printf
"%s\n%!" msg
652 if String.is_empty
body then
657 add_config config_json);
660 let (filename, _) = get_filename_and_path header in
667 "No 'error' field in error message")
672 ("Error processing " ^
filename ^
": " ^
error));
675 let (_, path
) = get_filename_and_path header in
677 get_field_opt
(get_bool
"is_systemlib") header
678 |> Option.value ~default
:false
680 let for_debugger_eval =
682 (get_bool
"for_debugger_eval")
684 fail_daemon None
("for_debugger_eval flag missing: " ^ af
))
687 add_config_overrides header;
688 let compiler_options =
689 { compiler_options with for_debugger_eval }
692 process_single_source_unit
694 ~
config_jsons:(get_config_jsons ())
696 Compile_ffi.{ include_header = true; output_file = None
}
705 (let (filename, path
) = get_filename_and_path header in
706 let body = body_or_file_contents body filename in
707 add_config_overrides header;
708 let (output
, hhbc_options
, log_config_json
) =
711 ~
config_jsons:(get_config_jsons ())
725 (new_debug_time ()));
728 (let (filename, path
) = get_filename_and_path header in
729 let body = body_or_file_contents body filename in
730 add_config_overrides header;
731 let (output
, hhbc_options
, log_config_json
) =
733 ~
config_jsons:(get_config_jsons ())
748 (new_debug_time ()));
751 dispatch_loop handlers
753 let handle_exception filename exc
=
754 let stack = Caml.Printexc.get_backtrace
() in
757 "Error in file %s: %s\n"
758 (Relative_path.to_absolute
filename)
759 (Caml.Printexc.to_string exc
)
761 let process_single_file output_file filename_str
=
763 Relative_path.create
Relative_path.Dummy filename_str
765 (* let abs_path = Relative_path.to_absolute filename in *)
766 let files = Multifile.file_to_file_list
filename in
767 List.iter
files ~f
:(fun (filename, content
) ->
768 process_single_source_unit
770 ~
config_jsons:(get_config_jsons ())
774 include_header = compiler_options.include_header;
780 if compiler_options.log_stats then
781 log_peak_mem (Some filename_str
) "compile"
783 let (filenames
, output_file) =
784 match compiler_options.input_file_list with
785 (* List of source files explicitly given *)
786 | Some
input_file_list ->
787 let get_lines_in_file filename =
788 let inch = Caml.open_in
filename in
790 match Caml.input_line
inch with
791 | line -> go (Caml.String.trim
line :: lines
)
792 | exception End_of_file
-> lines
796 (get_lines_in_file input_file_list, None
)
797 | None
-> ([compiler_options.filename], compiler_options.output_file)
798 (* Actually execute the compilation(s) *)
800 if compiler_options.dump_config then
802 "===CONFIG===\n%s\n\n%!"
803 ( Hhbc_options.apply_config_overrides_statelessly
804 compiler_options.config_list
805 (get_config_jsons ())
807 |> Hhbc_options.to_string
);
809 (not
(String.is_empty
compiler_options.filename))
810 && Sys.is_directory
compiler_options.filename
813 "%s is a directory, directory is not supported."
814 compiler_options.filename
817 if compiler_options.dump_desugared_expression_trees then
818 desugar_and_print_expr_trees
819 ~
config_jsons:(get_config_jsons ())
822 process_single_file output_file
824 List.iter filenames
process_fn))
827 let start_time = Unix.gettimeofday
() in
828 if opts
.log_stats then Logger.init_sync
start_time;
829 decl_and_run_mode opts
831 (* command line driver *)
833 Printexc.record_backtrace
true;
835 if !Sys.interactive
then
838 (* On windows, setting 'binary mode' avoids to output CRLF on
839 stdout. The 'text mode' would not hurt the user in general, but
840 it breaks the testsuite where the output is compared to the
841 expected one (i.e. in given file without CRLF). *)
842 Caml.set_binary_mode_out
Caml.stdout
true;
843 let handle = SharedMem.init ~num_workers
:0 SharedMem.empty_config
in
844 ignore
(handle : SharedMem.handle);
845 let options = parse_options () in
848 let stack = Caml.Printexc.get_backtrace
() in
850 die (Caml.Printexc.to_string exc
)