diff --git a/CHANGELOG.md b/CHANGELOG.md index ac40791f655..76551587577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ #### :house: Internal - Add the `-check-lam` compiler option, enable Lambda invariant checking in compiler tests, and remove build-profile-dependent checking. https://github.com/rescript-lang/rescript/pull/8534 +- Replace `-bs-diagnose` with `-debug-ir` and make IR diagnostic artifacts deterministic, compilation-local, and easy to clean. https://github.com/rescript-lang/rescript/pull/8535 # 13.0.0-alpha.5 diff --git a/compiler/bsc/rescript_compiler_main.ml b/compiler/bsc/rescript_compiler_main.ml index 86102d0701a..23b6dfbbb79 100644 --- a/compiler/bsc/rescript_compiler_main.ml +++ b/compiler/bsc/rescript_compiler_main.ml @@ -409,7 +409,9 @@ let command_line_flags : (string * Bsc_args.spec * string) array = ( "-bs-no-cross-module-opt", clear Js_config.cross_module_inline, "*internal* Disable cross module inlining(experimental)" ); - ("-bs-diagnose", set Js_config.diagnose, "*internal* More verbose output"); + ( "-debug-ir", + set Js_config.debug_ir, + "*internal* Dump compiler IR and enable Lam invariant checks" ); ( "-check-lam", set Js_config.check_lam, "*internal* Check Lam invariants after optimization passes" ); diff --git a/compiler/common/ext_log.ml b/compiler/common/ext_log.ml index 5f88492d6a0..2ae128d6ad3 100644 --- a/compiler/common/ext_log.ml +++ b/compiler/common/ext_log.ml @@ -26,7 +26,7 @@ type 'a logging = ('a, Format.formatter, unit, unit, unit, unit) format6 -> 'a (* TODO: add {[@.]} later for all *) let dwarn ?(__POS__ : (string * int * int * int) option) f = - if !Js_config.diagnose then + if !Js_config.debug_ir then match __POS__ with | None -> Format.fprintf Format.err_formatter ("WARN: " ^^ f ^^ "@.") | Some (file, line, _, _) -> diff --git a/compiler/common/js_config.ml b/compiler/common/js_config.ml index ac68b678d3a..bd73af9a595 100644 --- a/compiler/common/js_config.ml +++ b/compiler/common/js_config.ml @@ -32,7 +32,7 @@ let no_version_header = ref false let directives = ref [] let cross_module_inline = ref false -let diagnose = ref false +let debug_ir = ref false let check_lam = ref false (* let (//) = Filename.concat *) diff --git a/compiler/common/js_config.mli b/compiler/common/js_config.mli index e138de34b00..ac242e5d6c1 100644 --- a/compiler/common/js_config.mli +++ b/compiler/common/js_config.mli @@ -49,8 +49,8 @@ val directives : string list ref val cross_module_inline : bool ref (** cross module inline option *) -val diagnose : bool ref -(** diagnose option *) +val debug_ir : bool ref +(** dump intermediate representations and related diagnostics *) val check_lam : bool ref (** check Lam invariants after optimization passes *) diff --git a/compiler/core/dune b/compiler/core/dune index 3059ac475e6..261b0deda02 100644 --- a/compiler/core/dune +++ b/compiler/core/dune @@ -13,20 +13,8 @@ (action (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) -(rule - (target js_pass_debug.ml) - (deps js_pass_debug.cppo.ml) - (action - (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) - (rule (target lam_compile_main.ml) (deps lam_compile_main.cppo.ml) (action (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) - -(rule - (target lam_util.ml) - (deps lam_util.cppo.ml) - (action - (run %{bin:cppo} %{env:CPPO_FLAGS=} %{deps} -o %{target}))) diff --git a/compiler/core/ir_diagnostics.ml b/compiler/core/ir_diagnostics.ml new file mode 100644 index 00000000000..a6184b6bfda --- /dev/null +++ b/compiler/core/ir_diagnostics.ml @@ -0,0 +1,48 @@ +type t = {directory: string; mutable next_index: int} + +let is_artifact filename = + match Ext_filename.get_extension_maybe filename with + | ".lam" | ".lambda" | ".jsx" -> true + | _ -> false + +let remove_stale_artifacts directory = + Sys.readdir directory + |> Array.iter (fun filename -> + if is_artifact filename then + Misc.remove_file (Filename.concat directory filename)) + +let create ~output_prefix = + let directory = output_prefix ^ ".debug-ir" in + if Sys.file_exists directory then ( + if not (Ext_sys.is_directory_no_exn directory) then + failwith (Printf.sprintf "%s exists and is not a directory" directory); + remove_stale_artifacts directory) + else Sys.mkdir directory 0o755; + Ext_log.dwarn ~__POS__ "Writing IR diagnostics to %s" directory; + {directory; next_index = 1} + +let next_path diagnostics ~kind ~pass ~extension = + let index = diagnostics.next_index in + diagnostics.next_index <- index + 1; + Filename.concat diagnostics.directory + (Printf.sprintf "%02d-%s-%s%s" index kind pass extension) + +let dump_lam diagnostics ~pass lam = + let path = next_path diagnostics ~kind:"lam" ~pass ~extension:".lam" in + Ext_log.dwarn ~__POS__ "Dumping Lam pass %s to %s" pass path; + Lam_print.serialize path lam + +let dump_groups diagnostics groups = + let path = + next_path diagnostics ~kind:"lam" ~pass:"groups" ~extension:".lambda" + in + Ext_log.dwarn ~__POS__ "Dumping Lam groups to %s" path; + Ext_fmt.with_file_as_pp path (fun fmt -> + Format.pp_print_list ~pp_sep:Format.pp_print_newline Lam_group.pp_group + fmt groups) + +let dump_js diagnostics ~pass program = + let path = next_path diagnostics ~kind:"js" ~pass ~extension:".jsx" in + Ext_log.dwarn ~__POS__ "Dumping JS pass %s to %s" pass path; + Ext_pervasives.with_file_as_chan path (fun channel -> + Js_dump_program.dump_program program channel) diff --git a/compiler/core/ir_diagnostics.mli b/compiler/core/ir_diagnostics.mli new file mode 100644 index 00000000000..9ec4b0804d7 --- /dev/null +++ b/compiler/core/ir_diagnostics.mli @@ -0,0 +1,6 @@ +type t + +val create : output_prefix:string -> t +val dump_lam : t -> pass:string -> Lam.t -> unit +val dump_groups : t -> Lam_group.t list -> unit +val dump_js : t -> pass:string -> J.program -> unit diff --git a/compiler/core/js_pass_debug.cppo.ml b/compiler/core/js_pass_debug.cppo.ml deleted file mode 100644 index 16bd7470a87..00000000000 --- a/compiler/core/js_pass_debug.cppo.ml +++ /dev/null @@ -1,38 +0,0 @@ -(* Copyright (C) 2015-2016 Bloomberg Finance L.P. - * Copyright (C) 2017 - Hongbo Zhang, Authors of ReScript - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * In addition to the permissions granted to you by the LGPL, you may combine - * or link a "work that uses the Library" with a publicly distributed version - * of this file to produce a combined library or application, then distribute - * that combined work under the terms of your choosing, with no requirement - * to comply with the obligations normally placed on you by section 4 of the - * LGPL version 3 (or the corresponding section of a later version of the LGPL - * should you choose to use a later version). - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) - - - - - -let log_counter = ref 0 - -let dump name (prog : J.program) = - incr log_counter; - Ext_log.dwarn ~__POS__ "\n@[[TIME:]%s: %f@]@." name (Sys.time () *. 1000.); - Ext_pervasives.with_file_as_chan - (Ext_filename.new_extension !Location.input_name - (Printf.sprintf ".%02d.%s.jsx" !log_counter name)) - (fun chan -> Js_dump_program.dump_program prog chan); - prog diff --git a/compiler/core/js_pass_debug.mli b/compiler/core/js_pass_debug.mli deleted file mode 100644 index 1052769fc75..00000000000 --- a/compiler/core/js_pass_debug.mli +++ /dev/null @@ -1,25 +0,0 @@ -(* Copyright (C) 2015-2016 Bloomberg Finance L.P. - * Copyright (C) 2017 - Hongbo Zhang, Authors of ReScript - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * In addition to the permissions granted to you by the LGPL, you may combine - * or link a "work that uses the Library" with a publicly distributed version - * of this file to produce a combined library or application, then distribute - * that combined work under the terms of your choosing, with no requirement - * to comply with the obligations normally placed on you by section 4 of the - * LGPL version 3 (or the corresponding section of a later version of the LGPL - * should you choose to use a later version). - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) - -val dump : string -> J.program -> J.program diff --git a/compiler/core/lam_compile_main.cppo.ml b/compiler/core/lam_compile_main.cppo.ml index 59966e97b27..884c06afb02 100644 --- a/compiler/core/lam_compile_main.cppo.ml +++ b/compiler/core/lam_compile_main.cppo.ml @@ -116,21 +116,6 @@ let no_side_effects (rest : Lam_group.t list) : string option = else None (* TODO :*)) -let _d = fun s lam -> - let diagnose = !Js_config.diagnose in - if diagnose then begin - Lam_util.dump s lam; - Ext_log.dwarn ~__POS__ "START CHECKING PASS %s@." s - end; - if !Js_config.check_lam || diagnose then begin - ignore @@ Lam_check.check ~file:!Location.input_name ~pass:s lam; - if diagnose then Ext_log.dwarn ~__POS__ "FINISH CHECKING PASS %s@." s - end; - lam - -let _j name program = - if !Js_config.diagnose then Js_pass_debug.dump name program else program - (** Actually simplify_lets is kind of global optimization since it requires you to know whether it's used or not *) @@ -138,10 +123,31 @@ let compile (output_prefix : string) export_idents (lam : Lambda.lambda) = + let debug_ir = !Js_config.debug_ir in + let diagnostics = + if debug_ir then Some (Ir_diagnostics.create ~output_prefix) else None + in + let d pass lam = + (match diagnostics with + | Some diagnostics -> + Ir_diagnostics.dump_lam diagnostics ~pass lam; + Ext_log.dwarn ~__POS__ "START CHECKING PASS %s@." pass + | None -> ()); + if !Js_config.check_lam || debug_ir then begin + ignore @@ Lam_check.check ~file:!Location.input_name ~pass lam; + if debug_ir then Ext_log.dwarn ~__POS__ "FINISH CHECKING PASS %s@." pass + end; + lam + in + let j pass program = + Ext_option.iter diagnostics (fun diagnostics -> + Ir_diagnostics.dump_js diagnostics ~pass program); + program + in let export_ident_sets = Set_ident.of_list export_idents in (* To make toplevel happy - reentrant for js-demo *) let () = - if !Js_config.diagnose then begin + if debug_ir then begin Ext_list.iter export_idents (fun id -> Ext_log.dwarn ~__POS__ "export idents: %s/%d" id.name id.stamp) end; @@ -150,9 +156,9 @@ let compile let lam, may_required_modules = Lam_convert.convert export_ident_sets lam in - let lam = _d "initial" lam in + let lam = d "initial" lam in let lam = Lam_pass_deep_flatten.deep_flatten lam in - let lam = _d "flatten0" lam in + let lam = d "flatten0" lam in let meta : Lam_stats.t = Lam_stats.make ~export_idents @@ -161,19 +167,19 @@ let compile let lam = let lam = lam - |> _d "flattern1" + |> d "flatten1" |> Lam_pass_exits.simplify_exits - |> _d "simplyf_exits" + |> d "simplify_exits" |> (fun lam -> Lam_pass_collect.collect_info meta lam; - if !Js_config.diagnose then + if debug_ir then Ext_log.dwarn ~__POS__ "Before simplify_alias: %a@." Lam_stats.print meta; lam) |> Lam_pass_remove_alias.simplify_alias meta - |> _d "simplify_alias" + |> d "simplify_alias" |> Lam_pass_deep_flatten.deep_flatten - |> _d "flatten2" + |> d "flatten2" in (* Inling happens*) let () = Lam_pass_collect.collect_info meta lam in @@ -182,31 +188,31 @@ let compile let () = Lam_pass_collect.collect_info meta lam in let lam = lam - |> _d "alpha_before" + |> d "alpha_before" |> Lam_pass_alpha_conversion.alpha_conversion meta - |> _d "alpha_after" + |> d "alpha_after" |> Lam_pass_exits.simplify_exits in let () = Lam_pass_collect.collect_info meta lam in lam - |> _d "simplify_alias_before" + |> d "simplify_alias_before" |> Lam_pass_remove_alias.simplify_alias meta - |> _d "alpha_conversion" + |> d "alpha_conversion" |> Lam_pass_alpha_conversion.alpha_conversion meta - |> _d "before-simplify_lets" + |> d "before-simplify_lets" (* we should investigate a better way to put different passes : )*) |> Lam_pass_lets_dce.simplify_lets - |> _d "before-simplify-exits" + |> d "before-simplify-exits" (* |> (fun lam -> Lam_pass_collect.collect_info meta lam ; Lam_pass_remove_alias.simplify_alias meta lam) *) (* |> Lam_group_pass.scc_pass - |> _d "scc" *) + |> d "scc" *) |> Lam_pass_exits.simplify_exits - |> _d "simplify_lets" + |> d "simplify_lets" |> (fun lam -> - if !Js_config.diagnose then + if debug_ir then Ext_log.dwarn ~__POS__ "Before coercion: %a@." Lam_stats.print meta; lam) in @@ -216,19 +222,15 @@ let compile in let () = - if !Js_config.diagnose then begin + if debug_ir then begin Ext_log.dwarn ~__POS__ "After coercion: %a@." Lam_stats.print meta; - let f = - Ext_filename.new_extension !Location.input_name ".lambda" in - Ext_fmt.with_file_as_pp f begin fun fmt -> - Format.pp_print_list ~pp_sep:Format.pp_print_newline - Lam_group.pp_group fmt (coerced_input.groups) - end + Ext_option.iter diagnostics (fun diagnostics -> + Ir_diagnostics.dump_groups diagnostics coerced_input.groups) end in let maybe_pure = no_side_effects groups in let () = - if !Js_config.diagnose then + if debug_ir then Ext_log.dwarn ~__POS__ "\n@[[TIME:]Pre-compile: %f@]@." (Sys.time () *. 1000.) in @@ -238,7 +240,7 @@ let body = |> Js_output.output_as_block in let () = - if !Js_config.diagnose then + if debug_ir then Ext_log.dwarn ~__POS__ "\n@[[TIME:]Post-compile: %f@]@." (Sys.time () *. 1000.) in @@ -253,22 +255,22 @@ let js : J.program = block = body} in js -|> _j "initial" +|> j "initial" |> Js_pass_flatten.program -|> _j "flatten" +|> j "flatten" |> Js_pass_external_shadow.program -|> _j "external_shadow" +|> j "external_shadow" |> Js_pass_tailcall_inline.tailcall_inline -|> _j "inline_and_shake" +|> j "inline_and_shake" |> Js_pass_record_rest.program -|> _j "record_rest" +|> j "record_rest" |> Js_pass_flatten_and_mark_dead.program -|> _j "flatten_and_mark_dead" +|> j "flatten_and_mark_dead" (* |> Js_inline_and_eliminate.inline_and_shake *) -(* |> _j "inline_and_shake" *) +(* |> j "inline_and_shake" *) |> (fun js -> ignore @@ Js_pass_scope.program js ; js ) |> Js_shake.shake_program -|> _j "shake" +|> j "shake" |> ( fun (program: J.program) -> let external_module_ids : Lam_module_ident.t list = if !Js_config.all_module_aliases then [] diff --git a/compiler/core/lam_util.cppo.ml b/compiler/core/lam_util.ml similarity index 67% rename from compiler/core/lam_util.cppo.ml rename to compiler/core/lam_util.ml index 6a292c241fe..1e9bb57bc1e 100644 --- a/compiler/core/lam_util.cppo.ml +++ b/compiler/core/lam_util.ml @@ -22,22 +22,12 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) - - - - - - - - - (* let add_required_modules ( x : Ident.t list) (meta : Lam_stats.t) = let meta_require_modules = meta.required_modules in List.iter (fun x -> add meta_require_modules (Lam_module_ident.of_ml x)) x *) - (* refine_let normalises let-bindings so we avoid redundant locals while preserving the semantics encoded by Lambda's let_kind. Downstream passes at the JS backend interpret the k-tag as the shape of code they are allowed to @@ -65,7 +55,7 @@ let add_required_modules ( x : Ident.t list) (meta : Lam_stats.t) = Falling through keeps the original binding. Only the Alias clause changes evaluation strategy downstream, so we keep its predicate intentionally syntactic and narrow. *) - let refine_let ~kind param (arg : Lam.t) (l : Lam.t) : Lam.t = +let refine_let ~kind param (arg : Lam.t) (l : Lam.t) : Lam.t = let is_block_constructor = function | Lam_primitive.Pmakeblock _ -> true | _ -> false @@ -79,76 +69,78 @@ let add_required_modules ( x : Ident.t list) (meta : Lam_stats.t) = let rec is_safe_to_alias (lam : Lam.t) = match lam with | Lvar _ | Lconst _ -> - (* var/const --> emitting multiple `const` reads is identical to the + (* var/const --> emitting multiple `const` reads is identical to the original eager evaluation, so codegen may inline them freely. *) - true - | Lprim { primitive = Pfield (_, Fld_module _); args = [ (Lglobal_module _ | Lvar _) ]; _ } -> - (* field read --> access hits an immutable module block; inlining emits + true + | Lprim + { + primitive = Pfield (_, Fld_module _); + args = [(Lglobal_module _ | Lvar _)]; + _; + } -> + (* field read --> access hits an immutable module block; inlining emits the same read the eager binding would have performed once. *) - true - | Lprim { primitive = Psome_not_nest; args = [inner]; _ } -> - (* some_not_nest(inner) --> expands to two explicit rewrites: + true + | Lprim {primitive = Psome_not_nest; args = [inner]; _} -> + (* some_not_nest(inner) --> expands to two explicit rewrites: let[k] x = inner --> let[Alias] x = inner let[Alias] x = inner --> let[Alias] x = Some(inner) The recursive call discharges the first arrow; the constructor wrap is allocation-free in JS, so the second arrow preserves the single eager evaluation promised by Strict/StrictOpt. *) - is_safe_to_alias inner + is_safe_to_alias inner | _ -> false in - match (kind : Lam_compat.let_kind), arg, l with + match ((kind : Lam_compat.let_kind), arg, l) with | _, _, Lvar w when Ident.same w param -> - (* If the body immediately returns the binding (e.g. `{ let x = value; x }`), + (* If the body immediately returns the binding (e.g. `{ let x = value; x }`), we skip creating `x` and keep `value`. There is no `rec`, so `value` cannot refer back to `x`, and we avoid generating a redundant local. *) - arg - | _, _, Lprim { primitive; args = [ Lvar w ]; loc; _ } - when Ident.same w param && not (is_block_constructor primitive) -> - (* When we immediately feed the binding into a primitive, like + arg + | _, _, Lprim {primitive; args = [Lvar w]; loc; _} + when Ident.same w param && not (is_block_constructor primitive) -> + (* When we immediately feed the binding into a primitive, like `{ let x = value; Array.length(x) }`, we inline the primitive call with `value`. This only happens for primitives that are pure and do not allocate new blocks, so evaluation order and side effects stay the same. *) - Lam.prim ~primitive ~args:[arg] loc - | _, _, Lapply { ap_func = fn; ap_args = [ Lvar w ]; ap_info; ap_transformed_jsx } - when Ident.same w param && not (Lam_hit.hit_variable param fn) -> - (* For a function call such as `{ let x = value; someFn(x) }`, we can + Lam.prim ~primitive ~args:[arg] loc + | _, _, Lapply {ap_func = fn; ap_args = [Lvar w]; ap_info; ap_transformed_jsx} + when Ident.same w param && not (Lam_hit.hit_variable param fn) -> + (* For a function call such as `{ let x = value; someFn(x) }`, we can rewrite to `someFn(value)` as long as the callee does not capture `x`. This removes the temporary binding while preserving the call semantics. *) - Lam.apply fn [arg] ap_info ~ap_transformed_jsx + Lam.apply fn [arg] ap_info ~ap_transformed_jsx | (Strict | StrictOpt), arg, _ when is_safe_to_alias arg -> - (* `Strict` and `StrictOpt` bindings both evaluate the RHS immediately + (* `Strict` and `StrictOpt` bindings both evaluate the RHS immediately (with `StrictOpt` allowing later elimination if unused). When that RHS is pure — `{ let x = Some(value); ... }`, `{ let x = 3; ... }`, or a module field read — we mark it as an alias so downstream passes can inline the original expression and drop the temporary. *) - Lam.let_ Alias param arg l + Lam.let_ Alias param arg l | Strict, Lfunction _, _ -> - (* If we eagerly evaluate a function binding such as + (* If we eagerly evaluate a function binding such as `{ let makeGreeting = () => "hi"; ... }`, we end up allocating the closure immediately. Downgrading `Strict` to `StrictOpt` preserves the original laziness while still letting later passes inline when safe. *) - Lam.let_ StrictOpt param arg l + Lam.let_ StrictOpt param arg l | Strict, _, _ when Lam_analysis.no_side_effects arg -> - (* A strict binding whose expression has no side effects — think + (* A strict binding whose expression has no side effects — think `{ let x = computePure(); use(x); }` — can be relaxed to `StrictOpt`. This keeps the original semantics yet allows downstream passes to skip evaluating `x` when it turns out to be unused. *) - Lam.let_ StrictOpt param arg l - | kind, _, _ -> - Lam.let_ kind param arg l + Lam.let_ StrictOpt param arg l + | kind, _, _ -> Lam.let_ kind param arg l -let alias_ident_or_global (meta : Lam_stats.t) (k:Ident.t) (v:Ident.t) - (v_kind : Lam_id_kind.t) = +let alias_ident_or_global (meta : Lam_stats.t) (k : Ident.t) (v : Ident.t) + (v_kind : Lam_id_kind.t) = (* treat rec as Strict, k is assigned to v {[ let k = v ]} *) - match v_kind with - | NA -> - begin - match Hash_ident.find_opt meta.ident_tbl v with - | None -> () - | Some ident_info -> Hash_ident.add meta.ident_tbl k ident_info - end + match v_kind with + | NA -> ( + match Hash_ident.find_opt meta.ident_tbl v with + | None -> () + | Some ident_info -> Hash_ident.add meta.ident_tbl k ident_info) | ident_info -> Hash_ident.add meta.ident_tbl k ident_info (* share -- it is safe to share most properties, @@ -164,10 +156,6 @@ let alias_ident_or_global (meta : Lam_stats.t) (k:Ident.t) (v:Ident.t) mutable reference *) - - - - (* How we destruct the immutable block depend on the block name itself, good hints to do aggressive destructing @@ -185,79 +173,67 @@ let alias_ident_or_global (meta : Lam_stats.t) (k:Ident.t) (v:Ident.t) mutable fields are explicit, since wen can not inline an mutable block access *) -let element_of_lambda (lam : Lam.t) : Lam_id_kind.element = - match lam with - | Lvar _ - | Lconst _ - | Lprim {primitive = Pfield (_, Fld_module _) ; - args = [ Lglobal_module _ | Lvar _ ]; - _} -> SimpleForm lam +let element_of_lambda (lam : Lam.t) : Lam_id_kind.element = + match lam with + | Lvar _ | Lconst _ + | Lprim + { + primitive = Pfield (_, Fld_module _); + args = [(Lglobal_module _ | Lvar _)]; + _; + } -> + SimpleForm lam (* | Lfunction _ *) - | _ -> NA - -let kind_of_lambda_block (xs : Lam.t list) : Lam_id_kind.t = - ImmutableBlock( Ext_array.of_list_map xs (fun x -> - element_of_lambda x )) - -let field_flatten_get - lam v i info (tbl : Lam_id_kind.t Hash_ident.t) : Lam.t = - match Hash_ident.find_opt tbl v with - | Some (Module g) -> - Lam.prim ~primitive:(Pfield (i, info)) - ~args:[ Lam.global_module g ] Location.none - | Some (ImmutableBlock (arr)) -> - begin match arr.(i) with - | NA -> lam () - | SimpleForm l -> l - | exception _ -> lam () - end - | Some (Constant (Const_block (_, Blk_record {fields}, ls))) -> - (match info with - | Fld_record {name} -> - let found = ref None in - for i = 0 to Array.length fields - 1 do - if fst(fields.(i)) = name then found := Ext_list.nth_opt ls i done; - (match !found with - | Some c when not (Lam_constant.is_allocating c) -> Lam.const c - | _ -> lam()) - | _ -> lam () - ) - | Some (Constant (Const_block (_,_,ls))) -> - begin match Ext_list.nth_opt ls i with - | None -> lam () - | Some x when not (Lam_constant.is_allocating x) -> Lam.const x - | Some _ -> lam () - end - | Some _ - | None -> lam () - -let log_counter = ref 0 -let dump ext lam = - incr log_counter; - Ext_log.dwarn ~__POS__ "\n@[[TIME:]%s: %f@]@." ext (Sys.time () *. 1000.); - Lam_print.serialize - (Ext_filename.new_extension !Location.input_name - (Printf.sprintf ".%02d%s.lam" !log_counter ext)) - lam - - - - - -let is_function (lam : Lam.t) = - match lam with - | Lfunction _ -> true | _ -> false - -let not_function (lam : Lam.t) = - match lam with - | Lfunction _ -> false | _ -> true + | _ -> NA + +let kind_of_lambda_block (xs : Lam.t list) : Lam_id_kind.t = + ImmutableBlock (Ext_array.of_list_map xs (fun x -> element_of_lambda x)) + +let field_flatten_get lam v i info (tbl : Lam_id_kind.t Hash_ident.t) : Lam.t = + match Hash_ident.find_opt tbl v with + | Some (Module g) -> + Lam.prim + ~primitive:(Pfield (i, info)) + ~args:[Lam.global_module g] + Location.none + | Some (ImmutableBlock arr) -> ( + match arr.(i) with + | NA -> lam () + | SimpleForm l -> l + | exception _ -> lam ()) + | Some (Constant (Const_block (_, Blk_record {fields}, ls))) -> ( + match info with + | Fld_record {name} -> ( + let found = ref None in + for i = 0 to Array.length fields - 1 do + if fst fields.(i) = name then found := Ext_list.nth_opt ls i + done; + match !found with + | Some c when not (Lam_constant.is_allocating c) -> Lam.const c + | _ -> lam ()) + | _ -> lam ()) + | Some (Constant (Const_block (_, _, ls))) -> ( + match Ext_list.nth_opt ls i with + | None -> lam () + | Some x when not (Lam_constant.is_allocating x) -> Lam.const x + | Some _ -> lam ()) + | Some _ | None -> lam () + +let is_function (lam : Lam.t) = + match lam with + | Lfunction _ -> true + | _ -> false + +let not_function (lam : Lam.t) = + match lam with + | Lfunction _ -> false + | _ -> true (* let is_var (lam : Lam.t) id = match lam with | Lvar id0 -> Ident.same id0 id | _ -> false *) - (* TODO: we need create 1. a smart [let] combinator, reusable beta-reduction 2. [lapply fn args info] diff --git a/compiler/core/lam_util.mli b/compiler/core/lam_util.mli index 25e257665b5..f9b845da901 100644 --- a/compiler/core/lam_util.mli +++ b/compiler/core/lam_util.mli @@ -54,9 +54,6 @@ val alias_ident_or_global : val refine_let : kind:Lam_compat.let_kind -> Ident.t -> Lam.t -> Lam.t -> Lam.t -val dump : string -> Lam.t -> unit -(** [dump] when {!Js_config.is_same_file}*) - val not_function : Lam.t -> bool val is_function : Lam.t -> bool diff --git a/tests/build_tests/debug_ir/input.js b/tests/build_tests/debug_ir/input.js new file mode 100644 index 00000000000..b38e3d7352e --- /dev/null +++ b/tests/build_tests/debug_ir/input.js @@ -0,0 +1,44 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuildOrThrow, execClean } = setup(import.meta.dirname); +const diagnosticsDir = path.join( + import.meta.dirname, + "lib", + "bs", + "src", + "Main.debug-ir", +); + +await execClean(); + +try { + await execBuildOrThrow(); + + const artifacts = (await fs.readdir(diagnosticsDir)).sort(); + assert.ok(artifacts.includes("01-lam-initial.lam")); + assert.ok(artifacts.some(name => name.endsWith("-lam-groups.lambda"))); + assert.ok(artifacts.some(name => name.endsWith("-js-initial.jsx"))); + + const indexes = artifacts.map(name => Number.parseInt(name.slice(0, 2), 10)); + assert.deepEqual( + indexes, + Array.from({ length: artifacts.length }, (_, index) => index + 1), + ); + + const staleArtifact = path.join(diagnosticsDir, "99-stale.lam"); + await fs.writeFile(staleArtifact, "stale"); + const source = path.join(import.meta.dirname, "src", "Main.res"); + const now = new Date(); + await fs.utimes(source, now, now); + await execBuildOrThrow(); + await assert.rejects(fs.access(staleArtifact)); +} finally { + await execClean(); +} + +await assert.rejects(fs.access(diagnosticsDir)); diff --git a/tests/build_tests/debug_ir/rescript.json b/tests/build_tests/debug_ir/rescript.json new file mode 100644 index 00000000000..2d33efe3039 --- /dev/null +++ b/tests/build_tests/debug_ir/rescript.json @@ -0,0 +1,5 @@ +{ + "name": "debug_ir", + "sources": ["src"], + "compiler-flags": ["-debug-ir"] +} diff --git a/tests/build_tests/debug_ir/src/Main.res b/tests/build_tests/debug_ir/src/Main.res new file mode 100644 index 00000000000..cd298427b25 --- /dev/null +++ b/tests/build_tests/debug_ir/src/Main.res @@ -0,0 +1 @@ +let answer = 42