Skip to content

Copy a dependency's values only when they are read - #9194

Open
JonoPrest wants to merge 2 commits into
jono/defer-signature-copyfrom
jono/defer-dependency-value-copies
Open

JonoPrest wants to merge 2 commits into
jono/defer-signature-copyfrom
jono/defer-dependency-value-copies

Conversation

@JonoPrest

Copy link
Copy Markdown
Contributor

Stacked on #9192.

When a module first uses a dependency, Env builds that dependency's components and copies every value of its interface with the module path substitution. Dependents read only a few of them: on a 552 module React app about 5% of those values are ever read.

For the components of a .cmi signature, values are now table cells that make the copy on first read. This uses the same approach as #9192 to keep output identical:

  • The first time the process builds the components of an interface (same CRC), it builds them eagerly and records how many type ids each value takes.
  • After that, building the components reserves those ids for each value, and the copy is made with exactly those ids when the value is read. Every stamp and type id, and so .cmi, .cmj, JS and .cmt, stays the same.
  • A copy that takes a different number of ids is made again with fresh ids. This doesn't happen on any project, and the tests check for it.
  • Types, constructors, labels, module types, submodules, and components of aliases, functor applications and local modules are built as before. One shot bsc and the first use of an interface in a build are unchanged.

open tables take the cells too: an open keeps a function giving each component's binding, and Env.copy_types overrides go in a small table checked first.

The gain is real but modest, and this adds a fair amount of code to env.ml (about 300 lines). Paired benchmark, 20 interleaved clean builds per project, against #9192, user CPU with bootstrap 95% CI (for the full version, deferring types and labels too; this values only version measured the same head to head, with about 30% less code):

project 1 domain default domains
React app, 552 modules 0.967 [0.958, 0.978] 0.974 [0.952, 0.988]
backend service, 391 modules 0.973 [0.955, 0.984] 0.968 [0.936, 0.995]
library 0.952 [0.922, 0.991] 0.945 [0.923, 1.002]

Wall time at default domains is within noise on two of the three projects. Allocation drops about 2% on the React app. So this is a few percent of CPU, and only in the long running rewatch process. Happy to drop it if that isn't worth the extra machinery in env.ml.

Testing:

  • New ounit_deferred_components_tests.ml: eager vs deferred components of every runtime interface compared as marshalled results, unread values still reserving their ids, the fallback for a wrong count, a copy that raises, Env.copy_types through an open, and whole compiler requests (outputs, .cmt and error messages with functors, aliases, include, module type of, recursive modules, shadowing, first class modules) eager vs deferred, also on 4 domains at once.
  • JS, .cmi, .cmj, .ast, .cmt and .cmti are byte identical to master across three production codebases and their dependencies, at 1 and the default number of domains. make test and make test-rewatch pass.

@JonoPrest
JonoPrest added this pull request to stack #9195 November 3, 2026 11:38
JonoPrest added a commit that referenced this pull request Nov 3, 2026
The entry names #9194.

Signed-off-by: Jono Prest <[email protected]>
@JonoPrest

Copy link
Copy Markdown
Contributor Author

@codex review

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Changelog

💅 Polish

  • Build: copy the values of a dependency's interface only when a module reads them, after the first use of the same interface in a build, instead of copying all of them when the module first uses the dependency.

Semantic Diff

2 commits · Full page

Copy a dependency's values when they are read

6d73a8e · JonoPrest · Nov 03, 2026, 11:26 UTC

Why

Forcing a dependency's components copied every value of its interface with the module path substitution, although a dependent reads only a few of them: on a 552-module app, about 5 % of the values of .cmi components are ever read.

For the components of a .cmi signature as it was read, values are now table cells that make the copy on first read. Type ids are allocated in order and end up in .cmt files and in which nodes the backtracking trail logs, so the copies keep their ids: once the process has built the components of the same .cmi contents (same CRC) eagerly, it knows how many type ids each value takes, reserves them when the components are built and makes the copy with exactly those ids when the value is read. A copy that takes a different number is made again with fresh ids, like the deferred signature copies, and counted per domain for the tests. One-shot bsc and the first use of an interface in a build still build eagerly. Types, constructors, labels, extension constructors, module types, submodules and components of anything else (aliases, functor applications, local modules) are built as before.

The tables of an open take the cells too: an open keeps a function giving the binding of each component, and the bindings Env.copy_types replaces through an open in a table checked before the components.

Review

  • Can a deferred copy differ from the eager one apart from when it is made? The copied values are the unmarshalled .cmi signature, which the type checker never sees (it only gets copies), and the copy takes no stamps; the type ids are the eager ones, so every later stamp and id, and every operation on the copied nodes, is the same. The new unit tests compare marshalled results of eager and deferred components over every runtime interface, and whole requests (outputs, .cmt and error messages) using functors, aliases, include, module type of, recursive modules, shadowing and first-class modules, also on several domains at once.
  • Are the cells shared between requests or domains? No: components belong to the persistent structures of one request; only the count table is shared, behind a mutex, and its arrays are not changed once recorded.
Allocate a value's copy closure only when the copy is deferred

b9df7dd · JonoPrest · Nov 09, 2026, 14:34 UTC

Why

components_of_module_maker built a closure for the copy of every value it added, also when it then copied the value at once (the first time the process builds a .cmi's components, and for every module that is not part of a .cmi), and checked recorded counts against the signature by converting the count array to a list.

make_cell now takes the substitution and the declaration and builds a closure only for a deferred copy, and the counts are checked by walking the signature with an index. Which values are deferred, the type ids they take and the components built are unchanged.

Measured with scripts/compilerBenchmarks.js against the previous commit: allocated words -0.15% in total, -0.25% in the project builds (where components are built again in one process and their counts are known), -0.04% for the files compiled one by one. On a 552 module React app (clean build, one domain) the build allocates 0.2% fewer words.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T11:41:00.343967Z 8810538 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 8810538627

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@codecov

codecov Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.46392% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.02%. Comparing base (3220de0) to head (b9df7dd).

Files with missing lines Patch % Lines
compiler/ml/env.ml 82.35% 21 Missing ⚠️
...sts/ounit_tests/ounit_deferred_components_tests.ml 94.05% 16 Missing ⚠️
Additional details and impacted files
@@                      Coverage Diff                      @@
##           jono/defer-signature-copy    #9194      +/-   ##
=============================================================
+ Coverage                      81.95%   82.02%   +0.07%     
=============================================================
  Files                            457      458       +1     
  Lines                          65620    65964     +344     
=============================================================
+ Hits                           53780    54109     +329     
- Misses                         11840    11855      +15     
Files with missing lines Coverage Δ
tests/ounit_tests/ounit_tests_main.ml 100.00% <ø> (ø)
...sts/ounit_tests/ounit_deferred_components_tests.ml 94.05% <94.05%> (ø)
compiler/ml/env.ml 81.46% <82.35%> (+1.33%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pkg-pr-new

pkg-pr-new Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript@9194

@rescript/belt

npm i https://pkg.pr.new/@rescript/belt@9194

@rescript/darwin-arm64

npm i https://pkg.pr.new/@rescript/darwin-arm64@9194

@rescript/linux-arm64

npm i https://pkg.pr.new/@rescript/linux-arm64@9194

@rescript/linux-x64

npm i https://pkg.pr.new/@rescript/linux-x64@9194

@rescript/runtime

npm i https://pkg.pr.new/@rescript/runtime@9194

@rescript/win32-x64

npm i https://pkg.pr.new/@rescript/win32-x64@9194

commit: b9df7dd

@JonoPrest
JonoPrest marked this pull request as ready for review November 3, 2026 11:56
@JonoPrest
JonoPrest requested a review from cknitt November 3, 2026 11:56
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 8810538 to 67eb3bc Compare November 3, 2026 19:51
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 67eb3bc to 94bd206 Compare November 3, 2026 21:51
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 94bd206 to c342131 Compare November 4, 2026 06:15
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from c342131 to 87d814f Compare November 4, 2026 17:14
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 87d814f to f8f3ba1 Compare November 9, 2026 07:18
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

🎉 Compiler benchmarks

Allocated words

In total: 680,168,100 → 675,146,415 (-0.74%).

🎉 Improvement: 2 benchmarks allocate more than 1% less.

Benchmark Base Head Change
build/clean 244,309,699 240,060,993 -1.74%
build/shared-edit 74,444,983 73,388,625 -1.42%
packages/@rescript/runtime/Obj.res 99,068 99,650 +0.59%
packages/@rescript/runtime/Primitive_hash.resi 102,836 103,434 +0.58%
packages/@rescript/runtime/Primitive_module.resi 103,253 103,851 +0.58%
packages/@rescript/runtime/Stdlib_File.res 95,471 96,018 +0.57%
packages/@rescript/belt/src/Belt_internalBucketsType.resi 166,486 167,414 +0.56%
packages/@rescript/runtime/Primitive_option.resi 122,694 123,377 +0.56%
packages/@rescript/belt/src/Belt_Int.resi 149,910 150,737 +0.55%
packages/@rescript/belt/src/Belt_internalSetBuckets.resi 201,267 202,375 +0.55%
221 more changes
Benchmark Base Head Change
packages/@rescript/belt/src/Belt_Float.resi 151,386 152,213 +0.55%
packages/@rescript/runtime/Primitive_list.resi 91,963 92,455 +0.53%
packages/@rescript/runtime/Primitive_float_extern.res 93,508 94,000 +0.53%
packages/@rescript/runtime/Stdlib_Blob.res 93,626 94,118 +0.53%
packages/@rescript/runtime/RescriptTools_ExtractCodeBlocks.res 112,158 112,732 +0.51%
packages/@rescript/runtime/Stdlib_AsyncGenerator.resi 169,367 170,228 +0.51%
packages/@rescript/runtime/Primitive_promise.res 97,025 97,517 +0.51%
packages/@rescript/runtime/Stdlib_Bool.resi 116,916 117,508 +0.51%
packages/@rescript/runtime/Primitive_int_extern.res 97,797 98,289 +0.50%
packages/@rescript/runtime/Primitive_string_extern.res 98,149 98,641 +0.50%
packages/@rescript/runtime/Stdlib_Iterable.resi 98,380 98,872 +0.50%
packages/@rescript/runtime/Stdlib_AsyncIterable.resi 98,527 99,019 +0.50%
packages/@rescript/belt/src/Belt_Float.res 166,124 166,953 +0.50%
packages/@rescript/runtime/Stdlib_Iterable.res 99,090 99,584 +0.50%
packages/@rescript/runtime/Stdlib_AsyncIterable.res 99,161 99,655 +0.50%
packages/@rescript/belt/src/Belt_Int.res 166,793 167,622 +0.50%
packages/@rescript/runtime/Primitive_object.resi 117,432 118,014 +0.50%
packages/@rescript/runtime/Stdlib_Generator.resi 162,881 163,681 +0.49%
packages/@rescript/runtime/Primitive_exceptions.resi 100,346 100,838 +0.49%
packages/@rescript/belt/src/Belt_MutableStack.resi 170,580 171,407 +0.48%
packages/@rescript/belt/src/Belt_SortArrayString.resi 170,735 171,562 +0.48%
packages/@rescript/belt/src/Belt_SortArrayInt.resi 170,853 171,680 +0.48%
packages/@rescript/runtime/RescriptTools.res 104,273 104,777 +0.48%
packages/@rescript/runtime/Primitive_dict.res 102,946 103,438 +0.48%
packages/@rescript/runtime/Stdlib_AsyncIterableIterator.resi 162,551 163,322 +0.47%
packages/@rescript/runtime/Stdlib_Type.resi 136,039 136,683 +0.47%
packages/@rescript/belt/src/Belt_internalBuckets.resi 234,485 235,593 +0.47%
packages/@rescript/belt/src/Belt_HashSetString.resi 176,426 177,253 +0.47%
packages/@rescript/belt/src/Belt_HashSetInt.resi 176,539 177,366 +0.47%
packages/@rescript/belt/src/Belt.res 188,310 189,188 +0.47%
packages/@rescript/belt/src/Belt_Range.resi 177,420 178,247 +0.47%
packages/@rescript/runtime/Stdlib_TaggedTemplate.resi 106,711 107,203 +0.46%
packages/@rescript/runtime/Stdlib_ArrayBuffer.res 107,398 107,892 +0.46%
packages/@rescript/runtime/Stdlib_TaggedTemplate.res 115,959 116,480 +0.45%
packages/@rescript/runtime/Stdlib_ArrayBuffer.resi 111,098 111,590 +0.44%
packages/@rescript/belt/src/Belt_internalBucketsType.res 299,049 300,356 +0.44%
packages/@rescript/runtime/Stdlib_Lazy.resi 112,656 113,148 +0.44%
packages/@rescript/runtime/Stdlib_IterableIterator.resi 151,771 152,430 +0.43%
packages/@rescript/runtime/Primitive_array_extern.res 114,682 115,174 +0.43%
packages/@rescript/runtime/Primitive_object_extern.res 114,886 115,378 +0.43%
packages/@rescript/runtime/Stdlib_Intl_Segments.res 116,784 117,276 +0.42%
packages/@rescript/belt/src/Belt_HashSet.resi 226,995 227,948 +0.42%
packages/@rescript/runtime/Stdlib_AsyncGenerator.res 224,209 225,136 +0.41%
packages/@rescript/belt/src/Belt_Id.resi 261,699 262,778 +0.41%
packages/@rescript/runtime/Dom_storage2.res 120,094 120,586 +0.41%
packages/@rescript/runtime/Stdlib_Bool.res 147,372 147,969 +0.41%
packages/@rescript/runtime/Jsx.res 122,250 122,742 +0.40%
packages/@rescript/runtime/Stdlib_Intl_Segmenter.res 155,044 155,667 +0.40%
packages/@rescript/runtime/Stdlib_Temporal_Now.res 143,323 143,898 +0.40%
packages/@rescript/runtime/Stdlib_JsExn.resi 123,204 123,696 +0.40%
packages/@rescript/runtime/Stdlib_WeakSet.res 123,316 123,808 +0.40%
packages/@rescript/runtime/Stdlib_Null.resi 208,577 209,405 +0.40%
packages/@rescript/belt/src/Belt_MutableQueue.resi 209,644 210,471 +0.39%
packages/@rescript/runtime/Stdlib_Intl_NumberFormat_Grouping.res 160,432 161,058 +0.39%
packages/@rescript/runtime/Stdlib_Generator.res 209,865 210,667 +0.38%
packages/@rescript/belt/src/Belt_HashMapString.resi 216,537 217,364 +0.38%
packages/@rescript/belt/src/Belt_HashMapInt.resi 216,652 217,479 +0.38%
packages/@rescript/belt/src/Belt_Id.res 296,375 297,470 +0.37%
packages/@rescript/runtime/Stdlib_IterableIterator.res 186,318 187,006 +0.37%
packages/@rescript/runtime/Stdlib_JsExn.res 177,474 178,122 +0.37%
packages/@rescript/runtime/Stdlib_AsyncIterableIterator.res 240,973 241,846 +0.36%
packages/@rescript/runtime/Stdlib_WeakMap.res 136,210 136,702 +0.36%
packages/@rescript/runtime/Stdlib_Intl_Collator.res 180,536 181,188 +0.36%
packages/@rescript/runtime/Primitive_string.res 163,784 164,364 +0.35%
packages/@rescript/runtime/Stdlib_Global.res 142,110 142,610 +0.35%
packages/@rescript/runtime/Dom_storage.res 170,048 170,635 +0.35%
packages/@rescript/runtime/Primitive_float.res 149,797 150,310 +0.34%
packages/@rescript/runtime/Stdlib_Symbol.res 144,784 145,278 +0.34%
packages/@rescript/runtime/Stdlib_Nullable.resi 204,071 204,764 +0.34%
packages/@rescript/belt/src/Belt_Option.resi 246,322 247,149 +0.34%
packages/@rescript/runtime/Primitive_exceptions.res 208,091 208,789 +0.34%
packages/@rescript/belt/src/Belt_Result.resi 248,112 248,939 +0.33%
packages/@rescript/runtime/Stdlib_Map.res 210,211 210,909 +0.33%
packages/@rescript/runtime/Stdlib_Ordering.res 154,198 154,708 +0.33%
packages/@rescript/runtime/Primitive_bool.res 149,569 150,061 +0.33%
packages/@rescript/runtime/Primitive_int.res 176,439 177,019 +0.33%
packages/@rescript/runtime/Stdlib_AsyncIterator.resi 182,243 182,842 +0.33%
packages/@rescript/belt/src/Belt_SortArray.resi 256,693 257,530 +0.33%
packages/@rescript/runtime/Stdlib_RegExp.res 164,302 164,837 +0.33%
packages/@rescript/runtime/Stdlib_Set.res 214,420 215,118 +0.33%
packages/@rescript/runtime/Stdlib_Intl_ListFormat.res 170,870 171,422 +0.32%
packages/@rescript/runtime/Stdlib_JsError.resi 186,314 186,913 +0.32%
packages/@rescript/runtime/Stdlib_Int.res 470,386 471,889 +0.32%
packages/@rescript/runtime/Stdlib_Intl_Common.res 154,078 154,570 +0.32%
packages/@rescript/runtime/Stdlib_Global.resi 156,489 156,985 +0.32%
packages/@rescript/runtime/Stdlib_Intl.res 174,400 174,952 +0.32%
packages/@rescript/runtime/Stdlib_Type.res 204,636 205,283 +0.32%
packages/@rescript/belt/src/Belt_SetString.resi 262,169 262,996 +0.32%
packages/@rescript/belt/src/Belt_SetInt.resi 262,296 263,123 +0.32%
packages/@rescript/belt/src/Belt_HashMap.resi 303,534 304,487 +0.31%
packages/@rescript/runtime/Stdlib_BigInt.res 209,648 210,306 +0.31%
packages/@rescript/runtime/Stdlib_Map.resi 221,763 222,456 +0.31%
packages/@rescript/belt/src/Belt_MutableSetString.resi 267,394 268,221 +0.31%
packages/@rescript/belt/src/Belt_MutableSetInt.resi 267,511 268,338 +0.31%
packages/@rescript/runtime/Stdlib_Symbol.resi 160,527 161,019 +0.31%
packages/@rescript/runtime/Stdlib_Iterator.resi 161,607 162,099 +0.30%
packages/@rescript/runtime/Stdlib_Float.resi 195,513 196,107 +0.30%
packages/@rescript/runtime/Stdlib_Intl_RelativeTimeFormat.res 181,701 182,253 +0.30%
packages/@rescript/runtime/Stdlib_JSON.resi 239,448 240,175 +0.30%
packages/@rescript/runtime/Primitive_array.res 201,164 201,765 +0.30%
packages/@rescript/runtime/Stdlib_Set.resi 232,730 233,423 +0.30%
packages/@rescript/runtime/Stdlib_Pair.res 168,677 169,172 +0.29%
packages/@rescript/runtime/Stdlib_Temporal_PlainMonthDay.res 271,138 271,925 +0.29%
packages/@rescript/belt/src/Belt_Range.res 300,604 301,475 +0.29%
packages/@rescript/belt/src/Belt_internalSetBuckets.res 758,364 760,549 +0.29%
packages/@rescript/runtime/Stdlib_Int.resi 207,401 207,995 +0.29%
packages/@rescript/runtime/Stdlib_Temporal_Instant.res 310,620 311,507 +0.29%
packages/@rescript/runtime/Stdlib_Float.res 221,412 222,042 +0.28%
packages/@rescript/runtime/Primitive_bigint.res 181,993 182,509 +0.28%
packages/@rescript/runtime/Stdlib_Null.res 349,287 350,275 +0.28%
packages/@rescript/runtime/Stdlib_RegExp.resi 189,434 189,966 +0.28%
packages/@rescript/runtime/Stdlib_Intl_Locale.res 200,033 200,585 +0.28%
packages/@rescript/runtime/Stdlib_Temporal_PlainTime.res 323,011 323,898 +0.27%
packages/@rescript/runtime/Stdlib_IteratorObject.resi 241,315 241,974 +0.27%
packages/@rescript/runtime/Primitive_list.res 213,256 213,838 +0.27%
packages/@rescript/runtime/Stdlib_Intl_PluralRules.res 204,717 205,269 +0.27%
packages/@rescript/belt/src/Belt_Set.resi 448,125 449,327 +0.27%
packages/@rescript/runtime/Stdlib_Int8Array.res 344,496 345,402 +0.26%
packages/@rescript/runtime/Stdlib_Uint8Array.res 344,511 345,417 +0.26%
packages/@rescript/runtime/Stdlib_Float32Array.res 344,614 345,520 +0.26%
packages/@rescript/runtime/Stdlib_Float64Array.res 344,614 345,520 +0.26%
packages/@rescript/runtime/Stdlib_Int16Array.res 344,643 345,549 +0.26%
packages/@rescript/runtime/Stdlib_Int32Array.res 344,645 345,551 +0.26%
packages/@rescript/runtime/Stdlib_Nullable.res 347,308 348,221 +0.26%
packages/@rescript/runtime/Stdlib_Uint16Array.res 344,650 345,556 +0.26%
packages/@rescript/runtime/Stdlib_Uint32Array.res 344,650 345,556 +0.26%
packages/@rescript/runtime/Stdlib_BigInt64Array.res 344,676 345,582 +0.26%
packages/@rescript/runtime/Stdlib_BigUint64Array.res 344,750 345,656 +0.26%
packages/@rescript/runtime/Stdlib_Uint8ClampedArray.res 345,026 345,932 +0.26%
packages/@rescript/runtime/Stdlib_BigInt.resi 187,632 188,124 +0.26%
packages/@rescript/runtime/Stdlib_Object.res 246,612 247,254 +0.26%
packages/@rescript/runtime/Primitive_option.res 315,583 316,403 +0.26%
packages/@rescript/runtime/Stdlib_AsyncIterator.res 312,308 313,117 +0.26%
packages/@rescript/runtime/Stdlib_Iterator.res 243,541 244,170 +0.26%
packages/@rescript/belt/src/Belt_MutableMapString.resi 320,721 321,548 +0.26%
packages/@rescript/belt/src/Belt_MutableMapInt.resi 320,839 321,666 +0.26%
packages/@rescript/runtime/Stdlib_JsError.res 235,517 236,124 +0.26%
packages/@rescript/belt/src/Belt_HashSetString.res 928,904 931,252 +0.25%
packages/@rescript/belt/src/Belt_internalAVLset.resi 371,135 372,073 +0.25%
packages/@rescript/runtime/Stdlib_Intl_DateTimeFormat.res 396,689 397,686 +0.25%
packages/@rescript/runtime/Stdlib_IteratorObject.res 264,462 265,123 +0.25%
packages/@rescript/runtime/Stdlib_Temporal.res 224,087 224,639 +0.25%
packages/@rescript/runtime/Stdlib_String.res 514,184 515,450 +0.25%
packages/@rescript/runtime/Stdlib_Math.res 332,282 333,098 +0.25%
packages/@rescript/runtime/Stdlib_Option.resi 241,296 241,888 +0.25%
packages/@rescript/runtime/Stdlib_DataView.res 231,933 232,502 +0.25%
packages/@rescript/belt/src/Belt_internalSetInt.res 785,447 787,373 +0.25%
packages/@rescript/belt/src/Belt_internalSetString.res 786,638 788,566 +0.25%
packages/@rescript/belt/src/Belt_HashSetInt.res 928,583 930,856 +0.24%
packages/@rescript/runtime/Stdlib_JSON.res 302,401 303,132 +0.24%
packages/@rescript/runtime/Primitive_module.res 356,903 357,764 +0.24%
packages/@rescript/belt/src/Belt_MutableSet.resi 407,240 408,195 +0.23%
packages/@rescript/belt/src/Belt_MutableMap.resi 407,642 408,595 +0.23%
packages/@rescript/runtime/Stdlib.res 984,338 986,623 +0.23%
packages/@rescript/belt/src/Belt_HashSet.res 1,088,452 1,090,966 +0.23%
packages/@rescript/runtime/Stdlib_Temporal_PlainYearMonth.res 387,162 388,049 +0.23%
packages/@rescript/belt/src/Belt_SetDict.resi 414,098 415,033 +0.23%
packages/@rescript/runtime/Stdlib_Dict.resi 245,063 245,615 +0.23%
packages/@rescript/belt/src/Belt_MapInt.res 1,327,212 1,330,185 +0.22%
packages/@rescript/belt/src/Belt_MapString.res 1,327,800 1,330,713 +0.22%
packages/@rescript/runtime/Stdlib_Temporal_PlainDate.res 407,183 408,070 +0.22%
packages/@rescript/runtime/Stdlib_Lazy.res 231,858 232,361 +0.22%
packages/@rescript/runtime/Stdlib_String.resi 504,840 505,934 +0.22%
packages/@rescript/belt/src/Belt_Map.resi 597,590 598,848 +0.21%
packages/@rescript/runtime/Stdlib_Dict.res 328,202 328,891 +0.21%
packages/@rescript/runtime/Stdlib_DataView.resi 271,300 271,867 +0.21%
packages/@rescript/belt/src/Belt_Option.res 403,783 404,621 +0.21%
packages/@rescript/belt/src/Belt_MapString.resi 399,494 400,321 +0.21%
packages/@rescript/belt/src/Belt_MapInt.resi 399,537 400,364 +0.21%
packages/@rescript/belt/src/Belt_internalBuckets.res 1,073,837 1,076,046 +0.21%
packages/@rescript/runtime/Primitive_js_extern.res 241,641 242,136 +0.20%
packages/@rescript/runtime/RescriptTools_Docgen.resi 281,652 282,226 +0.20%
packages/@rescript/runtime/Stdlib_Temporal_Duration.res 378,144 378,914 +0.20%
packages/@rescript/belt/src/Belt_HashMapInt.res 1,185,319 1,187,730 +0.20%
packages/@rescript/belt/src/Belt_HashMapString.res 1,186,031 1,188,442 +0.20%
packages/@rescript/belt/src/Belt_MutableStack.res 414,137 414,978 +0.20%
packages/@rescript/runtime/Stdlib_Temporal_PlainDateTime.res 441,630 442,517 +0.20%
packages/@rescript/runtime/RescriptTools_Docgen.res 291,960 292,536 +0.20%
packages/@rescript/belt/src/Belt_internalAVLtree.resi 480,047 480,986 +0.20%
packages/@rescript/belt/src/Belt_SetString.res 1,406,643 1,409,370 +0.19%
packages/@rescript/runtime/Stdlib_Result.resi 305,576 306,168 +0.19%
packages/@rescript/runtime/Stdlib_Console.res 377,888 378,618 +0.19%
packages/@rescript/belt/src/Belt_SetInt.res 1,405,930 1,408,641 +0.19%
packages/@rescript/belt/src/Belt_MutableMapString.res 1,286,252 1,288,706 +0.19%
packages/@rescript/belt/src/Belt_HashMap.res 1,357,049 1,359,624 +0.19%
packages/@rescript/belt/src/Belt_Result.res 439,511 440,343 +0.19%
packages/@rescript/belt/src/Belt_MutableMapInt.res 1,285,967 1,288,386 +0.19%
packages/@rescript/belt/src/Belt_MutableQueue.res 847,820 849,402 +0.19%
packages/@rescript/belt/src/Belt_Set.res 927,874 929,580 +0.18%
packages/@rescript/belt/src/Belt_MapDict.resi 533,003 533,940 +0.18%
packages/@rescript/runtime/Stdlib_Console.resi 422,896 423,624 +0.17%
packages/@rescript/belt/src/Belt_Map.res 1,069,788 1,071,598 +0.17%
packages/@rescript/belt/src/Belt_SortArrayInt.res 1,448,166 1,450,576 +0.17%
packages/@rescript/belt/src/Belt_SortArrayString.res 1,449,964 1,452,372 +0.17%
packages/@rescript/belt/src/Belt_MutableMap.res 1,453,036 1,455,288 +0.15%
packages/@rescript/runtime/Stdlib_Option.res 806,215 807,464 +0.15%
packages/@rescript/belt/src/Belt_SortArray.res 1,562,959 1,565,363 +0.15%
packages/@rescript/runtime/Stdlib_Temporal_ZonedDateTime.res 581,246 582,133 +0.15%
packages/@rescript/runtime/Stdlib_Promise.res 371,640 372,201 +0.15%
packages/@rescript/runtime/Stdlib_Math.resi 333,094 333,590 +0.15%
packages/@rescript/runtime/Stdlib_Date.res 559,162 559,988 +0.15%
packages/@rescript/runtime/Stdlib_Intl_NumberFormat.res 412,978 413,574 +0.14%
packages/@rescript/belt/src/Belt_SetDict.res 1,625,079 1,627,395 +0.14%
packages/@rescript/runtime/Stdlib_List.resi 418,506 419,098 +0.14%
packages/@rescript/belt/src/Belt_MutableSetString.res 1,823,516 1,826,045 +0.14%
packages/@rescript/belt/src/Belt_MutableSetInt.res 1,822,279 1,824,802 +0.14%
packages/@rescript/belt/src/Belt_Array.resi 672,600 673,528 +0.14%
stress/NestedClosures.res 11,543,300 11,558,889 +0.14%
packages/@rescript/belt/src/Belt_internalMapString.res 1,603,791 1,605,926 +0.13%
packages/@rescript/runtime/Stdlib_Promise.resi 376,659 377,151 +0.13%
packages/@rescript/runtime/Stdlib_Temporal_Common.res 376,725 377,217 +0.13%
packages/@rescript/belt/src/Belt_MapDict.res 1,865,435 1,867,858 +0.13%
packages/@rescript/runtime/Stdlib_Array.resi 746,788 747,731 +0.13%
packages/@rescript/belt/src/Belt_internalMapInt.res 1,601,947 1,603,915 +0.12%
packages/@rescript/runtime/Primitive_hash.res 1,020,783 1,022,030 +0.12%
packages/@rescript/runtime/JsxDOM.res 2,832,182 2,835,607 +0.12%
packages/@rescript/runtime/JsxEvent.res 1,104,143 1,105,465 +0.12%
packages/@rescript/belt/src/Belt_MutableSet.res 2,110,547 2,112,952 +0.11%
packages/@rescript/runtime/Stdlib_Date.resi 624,420 625,097 +0.11%
packages/@rescript/belt/src/Belt_List.resi 791,431 792,258 +0.10%
packages/@rescript/runtime/Primitive_object.res 1,239,985 1,241,239 +0.10%

Instructions

In total: 9,664,627,709 → 9,667,238,667 (+0.03%).

Benchmark Base Head Change
stress/NestedClosures.res 546,919,662 547,715,538 +0.15%
packages/@rescript/runtime/JsxDOM.res 120,197,253 120,338,330 +0.12%
parse/RedBlackTree.res 14,949,441 14,934,202 -0.10%

Output size

In total: 25,570,910 → 25,572,544 (+0.01%).

None of the 13 benchmarks changed by 0.1% or more.

Pull request b9df7dd compared with base 462c00f in this run.

@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from f8f3ba1 to 1ddb5e1 Compare November 9, 2026 07:34
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 1ddb5e1 to 31cc3ad Compare November 9, 2026 12:01
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 31cc3ad to 787e4e0 Compare November 9, 2026 12:41
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 787e4e0 to 3f453f9 Compare November 9, 2026 13:05
Why:
    Forcing a dependency's components copied every value of its interface
    with the module path substitution, although a dependent reads only a
    few of them: on a 552-module app, about 5 % of the values of .cmi
    components are ever read.

For the components of a .cmi signature as it was read, values are now
table cells that make the copy on first read. Type ids are allocated in
order and end up in .cmt files and in which nodes the backtracking trail
logs, so the copies keep their ids: once the process has built the
components of the same .cmi contents (same CRC) eagerly, it knows how many
type ids each value takes, reserves them when the components are built and
makes the copy with exactly those ids when the value is read. A copy that
takes a different number is made again with fresh ids, like the deferred
signature copies, and counted per domain for the tests. One-shot bsc and
the first use of an interface in a build still build eagerly. Types,
constructors, labels, extension constructors, module types, submodules and
components of anything else (aliases, functor applications, local modules)
are built as before.

The tables of an open take the cells too: an open keeps a function giving
the binding of each component, and the bindings Env.copy_types replaces
through an open in a table checked before the components.

Review: Can a deferred copy differ from the eager one apart from when it is
made? The copied values are the unmarshalled .cmi signature, which the type
checker never sees (it only gets copies), and the copy takes no stamps; the
type ids are the eager ones, so every later stamp and id, and every
operation on the copied nodes, is the same. The new unit tests compare
marshalled results of eager and deferred components over every runtime
interface, and whole requests (outputs, .cmt and error messages) using
functors, aliases, include, module type of, recursive modules, shadowing
and first-class modules, also on several domains at once.

Review: Are the cells shared between requests or domains? No: components
belong to the persistent structures of one request; only the count table is
shared, behind a mutex, and its arrays are not changed once recorded.

Signed-off-by: Jono Prest <[email protected]>
Why:
    `components_of_module_maker` built a closure for the copy of every
    value it added, also when it then copied the value at once (the first
    time the process builds a .cmi's components, and for every module
    that is not part of a .cmi), and checked recorded counts against the
    signature by converting the count array to a list.

`make_cell` now takes the substitution and the declaration and builds a
closure only for a deferred copy, and the counts are checked by walking
the signature with an index. Which values are deferred, the type ids
they take and the components built are unchanged.

Measured with scripts/compilerBenchmarks.js against the previous commit:
allocated words -0.15% in total, -0.25% in the project builds (where
components are built again in one process and their counts are known),
-0.04% for the files compiled one by one. On a 552 module React app
(clean build, one domain) the build allocates 0.2% fewer words.

Signed-off-by: Jono Prest <[email protected]>
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 03a22d2 to b9df7dd Compare November 9, 2026 16:43

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant