From 78dce1e5d0b0ec883d17f93fa45a249946837af8 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 10 Oct 2026 10:11:10 +0000 Subject: [PATCH 1/3] Stop writing a copy of the .cmi into .cmt and .cmti files Nothing reads it; tools load the .cmi itself. Files with the copy can still be read. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Christoph Knittel --- compiler/core/js_implementation.ml | 7 +++--- compiler/ml/cmt_format.ml | 4 ++-- compiler/ml/cmt_format.mli | 4 +--- compiler/ml/cmt_format_common.ml | 5 ++-- .../platform/native/cmt_format_persistence.ml | 14 ++++------- .../playground/cmt_format_persistence.ml | 3 +-- compiler/ml/typemod.ml | 24 +++++++++---------- compiler/ml/typemod.mli | 8 +------ 8 files changed, 26 insertions(+), 43 deletions(-) diff --git a/compiler/core/js_implementation.ml b/compiler/core/js_implementation.ml index 5f97b7708f3..fbc93e894f6 100644 --- a/compiler/core/js_implementation.ml +++ b/compiler/core/js_implementation.ml @@ -60,11 +60,10 @@ let after_parsing_sig ppf outputprefix ast = Delayed_checks.force_delayed_checks (); Warnings.check_fatal (); let deprecated = Builtin_attributes.deprecated_of_sig ast in - let sg = - Env.save_signature ~deprecated sg modulename (outputprefix ^ ".cmi") - in + ignore + (Env.save_signature ~deprecated sg modulename (outputprefix ^ ".cmi")); Typemod.save_signature modulename tsg outputprefix !Location.input_name - initial_env sg; + initial_env; process_with_gentype (outputprefix ^ ".cmti")) let interface ~parser ppf ?outputprefix fname = diff --git a/compiler/ml/cmt_format.ml b/compiler/ml/cmt_format.ml index d96c006ac00..b47a98c1d48 100644 --- a/compiler/ml/cmt_format.ml +++ b/compiler/ml/cmt_format.ml @@ -1,6 +1,6 @@ include Cmt_format_common -let save_cmt filename modname binary_annots sourcefile initial_env cmi = +let save_cmt filename modname binary_annots sourcefile initial_env = Cmt_format_persistence.save_cmt filename modname binary_annots sourcefile - initial_env cmi; + initial_env; clear () diff --git a/compiler/ml/cmt_format.mli b/compiler/ml/cmt_format.mli index b8f32c9cfe9..7495341f941 100644 --- a/compiler/ml/cmt_format.mli +++ b/compiler/ml/cmt_format.mli @@ -83,10 +83,8 @@ val save_cmt : (* source file *) Env.t -> (* initial env *) - Cmi_format.cmi_infos option -> - (* if a .cmi was generated *) unit -(** [save_cmt filename modname binary_annots sourcefile initial_env cmi] +(** [save_cmt filename modname binary_annots sourcefile initial_env] writes a cmt(i) file. *) (* Miscellaneous functions *) diff --git a/compiler/ml/cmt_format_common.ml b/compiler/ml/cmt_format_common.ml index 6edbf1b114f..1dbe4e32d20 100644 --- a/compiler/ml/cmt_format_common.ml +++ b/compiler/ml/cmt_format_common.ml @@ -111,9 +111,8 @@ let input_cmt ic = (input_value ic : cmt_infos) it exists, and the cmt_infos, if it exists. Thus, it can be used with .cmi, .cmt and .cmti files. - .cmti files always contain a cmi_infos at the beginning. .cmt files - only contain a cmi_infos at the beginning if there is no associated - .cmti file. + The compiler no longer writes a cmi_infos in front of the cmt_infos in + .cmt and .cmti files, but files from older compilers may contain one. *) let read filename = (* Printf.fprintf stderr "Cmt_format.read %s\n%!" filename; *) diff --git a/compiler/ml/platform/native/cmt_format_persistence.ml b/compiler/ml/platform/native/cmt_format_persistence.ml index b98fef30e8c..56acd6ca540 100644 --- a/compiler/ml/platform/native/cmt_format_persistence.ml +++ b/compiler/ml/platform/native/cmt_format_persistence.ml @@ -19,16 +19,12 @@ let output_cmt output_channel cmt = output_string output_channel Config.cmt_magic_number; output_value output_channel (cmt : cmt_infos) -let save_cmt filename modname binary_annots sourcefile initial_env cmi = +(* Unlike OCaml, no copy of the .cmi is written in front of the cmt infos: + tools read the .cmi itself. *) +let save_cmt filename modname binary_annots sourcefile initial_env = if !Clflags.binary_annotations then Misc.output_to_bin_file_directly filename - (fun temp_file_name output_channel -> - let interface_digest = - match cmi with - | None -> None - | Some cmi -> - Some (Cmi_format.output_cmi temp_file_name output_channel cmi) - in + (fun _temp_file_name output_channel -> let cmt = { cmt_modname = modname; @@ -44,7 +40,7 @@ let save_cmt filename modname binary_annots sourcefile initial_env cmi = (if need_to_clear_env then keep_only_summary initial_env else initial_env); cmt_imports = List.sort compare (Env.imports ()); - cmt_interface_digest = interface_digest; + cmt_interface_digest = None; cmt_use_summaries = need_to_clear_env; cmt_extra_info = {deprecated_used = deprecated_uses ()}; } diff --git a/compiler/ml/platform/playground/cmt_format_persistence.ml b/compiler/ml/platform/playground/cmt_format_persistence.ml index 2fc0fe177c4..b0b34f8ef43 100644 --- a/compiler/ml/platform/playground/cmt_format_persistence.ml +++ b/compiler/ml/platform/playground/cmt_format_persistence.ml @@ -1,2 +1 @@ -let save_cmt _filename _modname _binary_annots _sourcefile _initial_env _cmi = - () +let save_cmt _filename _modname _binary_annots _sourcefile _initial_env = () diff --git a/compiler/ml/typemod.ml b/compiler/ml/typemod.ml index f2980860ee8..63d3f50e456 100644 --- a/compiler/ml/typemod.ml +++ b/compiler/ml/typemod.ml @@ -1738,7 +1738,7 @@ let type_implementation_more ?check_exists sourcefile outputprefix modulename so that value declarations which are not used internally but exported are not reported as being unused. *) Cmt_format.save_cmt (outputprefix ^ ".cmt") modulename - (Cmt_format.Implementation str) (Some sourcefile) initial_env None; + (Cmt_format.Implementation str) (Some sourcefile) initial_env; (str, coercion, finalenv, dclsig) (* identifier is useless might read from serialized cmi files*)) else @@ -1753,26 +1753,24 @@ let type_implementation_more ?check_exists sourcefile outputprefix modulename the value being exported. We can still capture unused declarations like "let x = true;; let x = 1;;", because in this case, the inferred signature contains only the last declaration. *) - (if not !Clflags.dont_write_files then - let deprecated = Builtin_attributes.deprecated_of_str ast in - let cmi = - Env.save_signature ?check_exists ~deprecated simple_sg modulename - (outputprefix ^ ".cmi") - in - Cmt_format.save_cmt (outputprefix ^ ".cmt") modulename - (Cmt_format.Implementation str) (Some sourcefile) initial_env - (Some cmi)); + if not !Clflags.dont_write_files then ( + let deprecated = Builtin_attributes.deprecated_of_str ast in + ignore + (Env.save_signature ?check_exists ~deprecated simple_sg modulename + (outputprefix ^ ".cmi")); + Cmt_format.save_cmt (outputprefix ^ ".cmt") modulename + (Cmt_format.Implementation str) (Some sourcefile) initial_env); (str, coercion, finalenv, simple_sg) with e -> Cmt_format.save_cmt (outputprefix ^ ".cmt") modulename (Cmt_format.Partial_implementation (Array.of_list (Cmt_format.get_saved_types ()))) - (Some sourcefile) initial_env None; + (Some sourcefile) initial_env; raise e -let save_signature modname tsg outputprefix source_file initial_env cmi = +let save_signature modname tsg outputprefix source_file initial_env = Cmt_format.save_cmt (outputprefix ^ ".cmti") modname - (Cmt_format.Interface tsg) (Some source_file) initial_env (Some cmi) + (Cmt_format.Interface tsg) (Some source_file) initial_env (* "Packaging" of several compilation units into one unit having them as sub-modules. *) diff --git a/compiler/ml/typemod.mli b/compiler/ml/typemod.mli index 70712f9095f..b4d66547c48 100644 --- a/compiler/ml/typemod.mli +++ b/compiler/ml/typemod.mli @@ -45,13 +45,7 @@ val type_open_ : Path.t * Env.t val save_signature : - string -> - Typedtree.signature -> - string -> - string -> - Env.t -> - Cmi_format.cmi_infos -> - unit + string -> Typedtree.signature -> string -> string -> Env.t -> unit type error = | Cannot_apply of module_type From 2fe0a31c4f6e2f3ae3f9a714b6220bcfd9649c04 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 10 Oct 2026 10:17:04 +0000 Subject: [PATCH 2/3] Add CHANGELOG entry Co-Authored-By: Claude Opus 5.5 Signed-off-by: Christoph Knittel --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ec920bec2f..733b2441923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ - Speed up inlining decisions for large functions. https://github.com/rescript-lang/rescript/pull/8771 - Speed up the rewriting of JS globals shadowed by local bindings in nested functions. https://github.com/rescript-lang/rescript/pull/8772 - Speed up dead code elimination of toplevel bindings in large modules. https://github.com/rescript-lang/rescript/pull/8773 +- Make .cmt and .cmti files smaller by no longer embedding a copy of the .cmi. https://github.com/rescript-lang/rescript/pull/8774 - Represent explicit expression braces as `Pexp_braces` in parsetree v1 and format `else` branches consistently with `if` branches. https://github.com/rescript-lang/rescript/pull/8678 - Omit redundant braces around multi-statement switch case bodies when formatting. https://github.com/rescript-lang/rescript/pull/8677 - Avoid running `rescript-schema-ppx` and `sury-ppx` on source files without an `@schema` annotation. https://github.com/rescript-lang/rescript/pull/8662 From 09b9a2aeb07617e3555685c79a4bb50afca4b73e Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 10 Oct 2026 10:46:38 +0000 Subject: [PATCH 3/3] Update the cmt layout documentation Co-Authored-By: Claude Opus 5.5 Signed-off-by: Christoph Knittel --- compiler/ml/cmt_format.mli | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/compiler/ml/cmt_format.mli b/compiler/ml/cmt_format.mli index 7495341f941..e6076e0e724 100644 --- a/compiler/ml/cmt_format.mli +++ b/compiler/ml/cmt_format.mli @@ -15,19 +15,16 @@ (** cmt and cmti files format. *) -(** The layout of a cmt file is as follows: - := \{\} \{cmt infos\} \{\} +(** The layout of a cmt or cmti file is as follows: + := \{\} \{cmt infos\} where is the cmi file format: := . - More precisely, the optional part must be present if and only if - the file is: - - a cmti, or - - a cmt, for a ml file which has no corresponding mli (hence no - corresponding cmti). + The compiler no longer writes the optional part: the signature is + read from the .cmi file. Files written by older compilers have it in a + cmti, and in the cmt of a module without an interface. - Thus, we provide a common reading function for cmi and cmt(i) - files which returns an option for each of the three parts: cmi - info, cmt info, source info. *) + [read] accepts cmi, cmt and cmti files, with or without the part, + and returns an option for the cmi info and the cmt info. *) open Typedtree