Skip to content

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

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

JonoPrest wants to merge 3 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 <jjprest@gmail.com>
@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.53708% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.07%. Comparing base (189209b) to head (4b6c1e4).

Files with missing lines Patch % Lines
compiler/ml/env.ml 82.78% 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                      82.01%   82.07%   +0.06%     
=============================================================
  Files                            457      458       +1     
  Lines                          65827    66174     +347     
=============================================================
+ Hits                           53985    54315     +330     
- Misses                         11842    11859      +17     
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 82.06% <82.78%> (+1.08%) ⬆️

... 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: 4b6c1e4

@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
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from b9df7dd to 737b3cf Compare November 9, 2026 22:04
@github-actions

github-actions Bot commented Nov 9, 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

3 commits · Full page

Copy a dependency's values when they are read

f77df90 · 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

879474f · 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.

Build the path of a component found through an open with its binding

4b6c1e4 · JonoPrest · Nov 10, 2026, 11:40 UTC

Why

Id_tbl.find_opened returned the binding and its position as a new pair, which find_name and find_all took apart again to build the path, so every lookup of a name found in the components of an open allocated three words more than before the open tables took the cells. In stress/RefsInLoop that moved each statement's allocations from 46272 to 46368 bytes, a Fibonacci number: with the minor heap the benchmarks run with, the objects of the 300 statements in the .cmt then fall on the same slot of the marshaller's position table (Fibonacci hashing of addresses, linear probing), and marshalling the .cmt took about 40M more instructions (+23% under cachegrind), though the file is byte-identical.

find_opened now gives the path and the binding, built from the pair stored in the table; update, which only Env.copy_types calls, looks the binding up as before. The bindings found, their order and the outputs are unchanged.

Measured with scripts/compilerBenchmarks.js against the previous commit: allocated words -0.02% in total, stress/NestedClosures -0.13%, and stress/RefsInLoop -5.4K words; with perf stat (s=64M as the benchmarks run) RefsInLoop takes 456.5M instructions, against 515.0M before and 456.1M for #9192 head.

Review

  • Does the instruction count of RefsInLoop now depend on luck? Yes, as before Copy a dependency's values only when they are read #9194: any change of 96 bytes per statement in this benchmark can land on a stride that hashes badly, without any work in the compiler. The fix removes the allocation that moved it there, which is worth having on its own.

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

🎉 Compiler benchmarks

Allocated words

In total: 637,171,344 → 631,941,561 (-0.82%).

Generated projects: 629,737,665 → 548,300,925 (-12.93%).

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

Benchmark Base Head Change
build/WideDeps/edit 166,053,127 140,629,829 -15.31%
build/WideDeps/clean 371,552,062 320,695,755 -13.69%
build/Namespace/clean 84,188,880 79,029,519 -6.13%
build/clean 235,876,826 231,534,352 -1.84%
build/shared-edit 72,086,415 70,972,605 -1.55%
packages/@rescript/runtime/Obj.res 98,939 99,521 +0.59%
packages/@rescript/runtime/Primitive_hash.resi 102,605 103,203 +0.58%
packages/@rescript/runtime/Primitive_module.resi 103,034 103,632 +0.58%
packages/@rescript/runtime/Stdlib_File.res 95,440 95,987 +0.57%
packages/@rescript/belt/src/Belt_internalSetBuckets.resi 194,073 195,181 +0.57%
223 more changes
Benchmark Base Head Change
packages/@rescript/belt/src/Belt_internalBucketsType.resi 164,545 165,473 +0.56%
packages/@rescript/runtime/Primitive_option.resi 122,096 122,779 +0.56%
packages/@rescript/belt/src/Belt_Int.resi 149,519 150,346 +0.55%
packages/@rescript/belt/src/Belt_Float.resi 150,995 151,822 +0.55%
packages/@rescript/runtime/Primitive_list.resi 91,763 92,255 +0.54%
packages/@rescript/runtime/Primitive_float_extern.res 93,474 93,966 +0.53%
packages/@rescript/runtime/Stdlib_Blob.res 93,638 94,130 +0.53%
packages/@rescript/runtime/Stdlib_AsyncGenerator.resi 165,688 166,549 +0.52%
packages/@rescript/runtime/RescriptTools_ExtractCodeBlocks.res 111,906 112,480 +0.51%
packages/@rescript/runtime/Primitive_promise.res 96,841 97,333 +0.51%
packages/@rescript/runtime/Stdlib_Bool.resi 116,584 117,176 +0.51%
packages/@rescript/runtime/Primitive_int_extern.res 97,691 98,183 +0.50%
packages/@rescript/belt/src/Belt_Float.res 164,924 165,753 +0.50%
packages/@rescript/runtime/Primitive_string_extern.res 98,009 98,501 +0.50%
packages/@rescript/runtime/Stdlib_Iterable.resi 98,010 98,502 +0.50%
packages/@rescript/runtime/Stdlib_AsyncIterable.resi 98,157 98,649 +0.50%
packages/@rescript/belt/src/Belt_Int.res 165,593 166,422 +0.50%
packages/@rescript/runtime/Stdlib_Iterable.res 98,720 99,214 +0.50%
packages/@rescript/runtime/Stdlib_AsyncIterable.res 98,791 99,285 +0.50%
packages/@rescript/runtime/Stdlib_Generator.resi 160,310 161,110 +0.50%
packages/@rescript/runtime/Primitive_object.resi 116,938 117,520 +0.50%
packages/@rescript/belt/src/Belt_internalBuckets.resi 222,850 223,958 +0.50%
packages/@rescript/runtime/Primitive_exceptions.resi 100,168 100,660 +0.49%
packages/@rescript/belt/src/Belt_MutableStack.resi 169,088 169,915 +0.49%
packages/@rescript/belt/src/Belt_SortArrayString.resi 169,409 170,236 +0.49%
packages/@rescript/belt/src/Belt_SortArrayInt.resi 169,527 170,354 +0.49%
packages/@rescript/runtime/RescriptTools.res 104,272 104,776 +0.48%
packages/@rescript/runtime/Stdlib_AsyncIterableIterator.resi 159,687 160,458 +0.48%
packages/@rescript/runtime/Primitive_dict.res 102,477 102,969 +0.48%
packages/@rescript/runtime/Stdlib_Type.resi 135,703 136,347 +0.47%
packages/@rescript/belt/src/Belt_HashSetString.resi 175,331 176,158 +0.47%
packages/@rescript/belt/src/Belt_HashSetInt.resi 175,444 176,271 +0.47%
packages/@rescript/belt/src/Belt_Range.resi 176,490 177,317 +0.47%
packages/@rescript/belt/src/Belt.res 188,322 189,200 +0.47%
packages/@rescript/runtime/Stdlib_TaggedTemplate.resi 105,989 106,481 +0.46%
packages/@rescript/runtime/Stdlib_ArrayBuffer.res 107,232 107,726 +0.46%
packages/@rescript/runtime/Stdlib_TaggedTemplate.res 115,173 115,694 +0.45%
packages/@rescript/runtime/Stdlib_ArrayBuffer.resi 110,932 111,424 +0.44%
packages/@rescript/runtime/Stdlib_IterableIterator.resi 149,259 149,918 +0.44%
packages/@rescript/belt/src/Belt_internalBucketsType.res 292,536 293,825 +0.44%
packages/@rescript/runtime/Stdlib_Lazy.resi 112,257 112,749 +0.44%
packages/@rescript/runtime/Stdlib_AsyncGenerator.res 211,527 212,454 +0.44%
packages/@rescript/runtime/Primitive_array_extern.res 114,009 114,501 +0.43%
packages/@rescript/runtime/Primitive_object_extern.res 114,394 114,886 +0.43%
packages/@rescript/belt/src/Belt_HashSet.resi 222,556 223,509 +0.43%
packages/@rescript/runtime/Stdlib_Intl_Segments.res 116,273 116,765 +0.42%
packages/@rescript/belt/src/Belt_Id.resi 259,910 260,989 +0.42%
packages/@rescript/runtime/Dom_storage2.res 119,645 120,137 +0.41%
packages/@rescript/runtime/Stdlib_Bool.res 146,213 146,807 +0.41%
packages/@rescript/runtime/Stdlib_Null.resi 204,164 204,992 +0.41%
packages/@rescript/runtime/Jsx.res 121,408 121,900 +0.41%
packages/@rescript/runtime/Stdlib_Intl_Segmenter.res 154,073 154,696 +0.40%
packages/@rescript/runtime/Stdlib_Temporal_Now.res 143,059 143,634 +0.40%
packages/@rescript/runtime/Stdlib_JsExn.resi 122,556 123,048 +0.40%
packages/@rescript/runtime/Stdlib_WeakSet.res 122,747 123,239 +0.40%
packages/@rescript/runtime/Stdlib_Generator.res 200,595 201,397 +0.40%
packages/@rescript/belt/src/Belt_MutableQueue.resi 207,096 207,923 +0.40%
packages/@rescript/runtime/Stdlib_Intl_NumberFormat_Grouping.res 159,691 160,317 +0.39%
packages/@rescript/belt/src/Belt_HashMapString.resi 213,468 214,295 +0.39%
packages/@rescript/belt/src/Belt_HashMapInt.resi 213,583 214,410 +0.39%
packages/@rescript/runtime/Stdlib_IterableIterator.res 178,887 179,575 +0.38%
packages/@rescript/runtime/Stdlib_AsyncIterableIterator.res 228,729 229,593 +0.38%
packages/@rescript/belt/src/Belt_Id.res 290,145 291,240 +0.38%
packages/@rescript/runtime/Stdlib_JsExn.res 173,436 174,084 +0.37%
packages/@rescript/runtime/Stdlib_Intl_Collator.res 178,206 178,858 +0.37%
packages/@rescript/runtime/Stdlib_WeakMap.res 135,088 135,580 +0.36%
packages/@rescript/runtime/Stdlib_Global.res 141,498 141,998 +0.35%
packages/@rescript/runtime/Dom_storage.res 166,992 167,579 +0.35%
packages/@rescript/runtime/Stdlib_Nullable.resi 199,462 200,155 +0.35%
packages/@rescript/runtime/Primitive_string.res 160,284 160,840 +0.35%
packages/@rescript/belt/src/Belt_Result.resi 239,962 240,789 +0.34%
packages/@rescript/runtime/Stdlib_Symbol.res 144,258 144,752 +0.34%
packages/@rescript/belt/src/Belt_Option.resi 243,132 243,959 +0.34%
packages/@rescript/runtime/Primitive_exceptions.res 202,046 202,732 +0.34%
packages/@rescript/runtime/Stdlib_Map.res 205,545 206,240 +0.34%
packages/@rescript/runtime/Stdlib_AsyncIterator.resi 177,586 178,185 +0.34%
packages/@rescript/runtime/Primitive_bool.res 147,489 147,981 +0.33%
packages/@rescript/runtime/Primitive_float.res 147,530 148,022 +0.33%
packages/@rescript/belt/src/Belt_SortArray.resi 253,747 254,584 +0.33%
packages/@rescript/runtime/Stdlib_Set.res 210,800 211,495 +0.33%
packages/@rescript/runtime/Stdlib_RegExp.res 162,989 163,524 +0.33%
packages/@rescript/runtime/Stdlib_Intl_ListFormat.res 169,327 169,879 +0.33%
packages/@rescript/runtime/Stdlib_JsError.resi 185,162 185,761 +0.32%
packages/@rescript/runtime/Stdlib_Ordering.res 152,275 152,767 +0.32%
packages/@rescript/runtime/Primitive_int.res 172,649 173,205 +0.32%
packages/@rescript/belt/src/Belt_HashMap.resi 296,663 297,616 +0.32%
packages/@rescript/runtime/Stdlib_Type.res 201,629 202,276 +0.32%
packages/@rescript/runtime/Stdlib_Intl_Common.res 154,090 154,582 +0.32%
packages/@rescript/belt/src/Belt_SetString.resi 259,605 260,432 +0.32%
packages/@rescript/belt/src/Belt_SetInt.resi 259,732 260,559 +0.32%
packages/@rescript/runtime/Stdlib_Map.resi 217,781 218,474 +0.32%
packages/@rescript/runtime/Stdlib_Global.resi 155,893 156,389 +0.32%
packages/@rescript/runtime/Stdlib_BigInt.res 206,994 207,652 +0.32%
packages/@rescript/runtime/Stdlib_Intl.res 174,086 174,638 +0.32%
packages/@rescript/belt/src/Belt_internalSetBuckets.res 704,340 706,564 +0.32%
packages/@rescript/runtime/Stdlib_Int.res 457,453 458,896 +0.32%
packages/@rescript/belt/src/Belt_MutableSetString.resi 264,665 265,492 +0.31%
packages/@rescript/belt/src/Belt_MutableSetInt.resi 264,782 265,609 +0.31%
packages/@rescript/runtime/Stdlib_Iterator.resi 159,160 159,652 +0.31%
packages/@rescript/runtime/Stdlib_Symbol.resi 160,001 160,493 +0.31%
packages/@rescript/runtime/Stdlib_JSON.resi 236,791 237,518 +0.31%
packages/@rescript/runtime/Stdlib_Intl_RelativeTimeFormat.res 179,968 180,520 +0.31%
packages/@rescript/runtime/Stdlib_Float.resi 194,555 195,149 +0.31%
packages/@rescript/runtime/Stdlib_Set.resi 229,761 230,454 +0.30%
packages/@rescript/runtime/Stdlib_Pair.res 165,781 166,273 +0.30%
packages/@rescript/runtime/Primitive_array.res 193,310 193,881 +0.30%
packages/@rescript/runtime/Stdlib_Null.res 333,974 334,959 +0.29%
packages/@rescript/runtime/Stdlib_Temporal_PlainMonthDay.res 268,201 268,988 +0.29%
packages/@rescript/runtime/Stdlib_Temporal_Instant.res 307,341 308,228 +0.29%
packages/@rescript/runtime/Stdlib_Int.resi 205,829 206,423 +0.29%
packages/@rescript/runtime/Stdlib_Float.res 218,714 219,338 +0.29%
packages/@rescript/belt/src/Belt_Range.res 290,985 291,814 +0.28%
packages/@rescript/runtime/Stdlib_RegExp.resi 188,153 188,685 +0.28%
packages/@rescript/runtime/Stdlib_IteratorObject.resi 235,144 235,803 +0.28%
packages/@rescript/runtime/Stdlib_Intl_Locale.res 197,249 197,801 +0.28%
packages/@rescript/runtime/Primitive_list.res 205,229 205,802 +0.28%
packages/@rescript/runtime/Stdlib_Temporal_PlainTime.res 319,933 320,820 +0.28%
packages/@rescript/runtime/Primitive_bigint.res 178,101 178,593 +0.28%
packages/@rescript/belt/src/Belt_Set.resi 437,107 438,309 +0.27%
packages/@rescript/runtime/Stdlib_Nullable.res 331,545 332,455 +0.27%
packages/@rescript/runtime/Stdlib_Intl_PluralRules.res 201,332 201,884 +0.27%
packages/@rescript/runtime/Stdlib_AsyncIterator.res 295,420 296,220 +0.27%
packages/@rescript/runtime/Stdlib_Iterator.res 235,288 235,917 +0.27%
packages/@rescript/belt/src/Belt_HashSetInt.res 858,785 861,060 +0.26%
packages/@rescript/belt/src/Belt_HashSetString.res 859,104 861,379 +0.26%
packages/@rescript/runtime/Stdlib_Int8Array.res 343,100 344,006 +0.26%
packages/@rescript/runtime/Stdlib_Uint8Array.res 343,115 344,021 +0.26%
packages/@rescript/runtime/Stdlib_Float32Array.res 343,218 344,124 +0.26%
packages/@rescript/runtime/Stdlib_Float64Array.res 343,218 344,124 +0.26%
packages/@rescript/runtime/Stdlib_Int16Array.res 343,247 344,153 +0.26%
packages/@rescript/runtime/Stdlib_Int32Array.res 343,249 344,155 +0.26%
packages/@rescript/runtime/Stdlib_Uint16Array.res 343,254 344,160 +0.26%
packages/@rescript/runtime/Stdlib_Uint32Array.res 343,254 344,160 +0.26%
packages/@rescript/runtime/Stdlib_BigInt64Array.res 343,280 344,186 +0.26%
packages/@rescript/runtime/Stdlib_BigUint64Array.res 343,354 344,260 +0.26%
packages/@rescript/runtime/Stdlib_BigInt.resi 186,464 186,956 +0.26%
packages/@rescript/belt/src/Belt_internalAVLset.resi 354,533 355,468 +0.26%
packages/@rescript/runtime/Stdlib_JsError.res 230,188 230,795 +0.26%
packages/@rescript/runtime/Stdlib_Uint8ClampedArray.res 343,630 344,536 +0.26%
packages/@rescript/runtime/Stdlib_IteratorObject.res 250,985 251,646 +0.26%
packages/@rescript/runtime/Primitive_option.res 299,032 299,819 +0.26%
packages/@rescript/belt/src/Belt_MutableMapString.resi 314,662 315,489 +0.26%
packages/@rescript/belt/src/Belt_MutableMapInt.resi 314,780 315,607 +0.26%
packages/@rescript/runtime/Stdlib_Object.res 245,103 245,745 +0.26%
packages/@rescript/runtime/Stdlib_Intl_DateTimeFormat.res 388,878 389,875 +0.26%
packages/@rescript/runtime/Stdlib_Option.resi 236,227 236,819 +0.25%
packages/@rescript/belt/src/Belt_internalSetInt.res 737,522 739,367 +0.25%
packages/@rescript/belt/src/Belt_internalSetString.res 738,713 740,558 +0.25%
packages/@rescript/runtime/Stdlib_String.res 504,582 505,842 +0.25%
packages/@rescript/runtime/Primitive_module.res 340,404 341,253 +0.25%
packages/@rescript/belt/src/Belt_HashSet.res 987,276 989,732 +0.25%
packages/@rescript/runtime/Stdlib_DataView.res 230,230 230,799 +0.25%
packages/@rescript/runtime/Stdlib_Math.res 327,003 327,810 +0.25%
packages/@rescript/runtime/Stdlib_Temporal.res 224,099 224,651 +0.25%
packages/@rescript/runtime/Stdlib_JSON.res 297,705 298,436 +0.25%
packages/@rescript/belt/src/Belt_MutableMap.resi 395,108 396,061 +0.24%
packages/@rescript/belt/src/Belt_MutableSet.resi 397,215 398,170 +0.24%
packages/@rescript/belt/src/Belt_MapInt.res 1,190,718 1,193,570 +0.24%
packages/@rescript/runtime/Stdlib_Temporal_PlainYearMonth.res 381,653 382,540 +0.23%
packages/@rescript/belt/src/Belt_SetDict.resi 402,363 403,298 +0.23%
packages/@rescript/runtime/Stdlib.res 982,354 984,636 +0.23%
packages/@rescript/belt/src/Belt_MapString.res 1,191,254 1,194,009 +0.23%
packages/@rescript/runtime/Stdlib_Dict.resi 241,170 241,722 +0.23%
packages/@rescript/runtime/Stdlib_Lazy.res 222,317 222,811 +0.22%
packages/@rescript/belt/src/Belt_internalBuckets.res 975,196 977,350 +0.22%
packages/@rescript/runtime/Stdlib_Temporal_PlainDate.res 403,010 403,897 +0.22%
packages/@rescript/runtime/Stdlib_String.resi 500,201 501,295 +0.22%
packages/@rescript/belt/src/Belt_Map.resi 579,102 580,360 +0.22%
packages/@rescript/belt/src/Belt_HashMapString.res 1,071,905 1,074,219 +0.22%
packages/@rescript/belt/src/Belt_HashMapInt.res 1,071,193 1,073,505 +0.22%
packages/@rescript/runtime/Stdlib_Dict.res 319,721 320,410 +0.22%
packages/@rescript/belt/src/Belt_MutableStack.res 390,501 391,330 +0.21%
packages/@rescript/belt/src/Belt_Option.res 391,814 392,643 +0.21%
packages/@rescript/belt/src/Belt_MapString.resi 390,934 391,761 +0.21%
packages/@rescript/belt/src/Belt_MapInt.resi 390,977 391,804 +0.21%
packages/@rescript/runtime/Stdlib_DataView.resi 269,597 270,164 +0.21%
packages/@rescript/runtime/RescriptTools_Docgen.resi 273,343 273,917 +0.21%
packages/@rescript/belt/src/Belt_MutableMapInt.res 1,164,526 1,166,952 +0.21%
packages/@rescript/belt/src/Belt_MutableMapString.res 1,164,807 1,167,233 +0.21%
packages/@rescript/belt/src/Belt_internalAVLtree.resi 452,048 452,983 +0.21%
packages/@rescript/runtime/Primitive_js_extern.res 239,550 240,045 +0.21%
packages/@rescript/runtime/Stdlib_Temporal_Duration.res 372,839 373,609 +0.21%
packages/@rescript/belt/src/Belt_HashMap.res 1,211,536 1,214,020 +0.21%
packages/@rescript/runtime/RescriptTools_Docgen.res 283,544 284,120 +0.20%
packages/@rescript/runtime/Stdlib_Temporal_PlainDateTime.res 437,075 437,962 +0.20%
packages/@rescript/belt/src/Belt_SetInt.res 1,301,798 1,304,428 +0.20%
packages/@rescript/belt/src/Belt_SetString.res 1,302,529 1,305,157 +0.20%
packages/@rescript/belt/src/Belt_Result.res 412,136 412,965 +0.20%
packages/@rescript/runtime/Stdlib_Result.resi 296,115 296,707 +0.20%
packages/@rescript/runtime/Stdlib_Console.res 369,929 370,659 +0.20%
packages/@rescript/belt/src/Belt_Set.res 865,243 866,922 +0.19%
packages/@rescript/belt/src/Belt_MutableQueue.res 799,854 801,394 +0.19%
packages/@rescript/belt/src/Belt_Map.res 989,111 990,924 +0.18%
packages/@rescript/belt/src/Belt_MapDict.resi 512,925 513,860 +0.18%
packages/@rescript/runtime/Stdlib_Console.resi 414,937 415,665 +0.18%
packages/@rescript/belt/src/Belt_MutableMap.res 1,314,002 1,316,239 +0.17%
packages/@rescript/runtime/Stdlib_Option.res 762,155 763,383 +0.16%
packages/@rescript/runtime/Stdlib_Promise.res 351,911 352,472 +0.16%
packages/@rescript/runtime/Stdlib_Temporal_ZonedDateTime.res 572,336 573,223 +0.15%
packages/@rescript/belt/src/Belt_SetDict.res 1,459,076 1,461,321 +0.15%
packages/@rescript/runtime/Stdlib_Math.resi 330,813 331,309 +0.15%
packages/@rescript/runtime/Stdlib_Date.res 551,554 552,377 +0.15%
packages/@rescript/runtime/Stdlib_Intl_NumberFormat.res 403,889 404,485 +0.15%
packages/@rescript/belt/src/Belt_SortArrayString.res 1,362,825 1,364,834 +0.15%
packages/@rescript/belt/src/Belt_SortArrayInt.res 1,361,029 1,363,032 +0.15%
packages/@rescript/runtime/Stdlib_List.resi 409,461 410,053 +0.14%
packages/@rescript/belt/src/Belt_MapDict.res 1,645,300 1,647,669 +0.14%
packages/@rescript/belt/src/Belt_Array.resi 649,939 650,867 +0.14%
packages/@rescript/belt/src/Belt_MutableSetInt.res 1,682,952 1,685,344 +0.14%
packages/@rescript/belt/src/Belt_MutableSetString.res 1,684,195 1,686,587 +0.14%
packages/@rescript/belt/src/Belt_internalMapInt.res 1,423,261 1,425,268 +0.14%
packages/@rescript/belt/src/Belt_internalMapString.res 1,425,105 1,427,112 +0.14%
packages/@rescript/runtime/Stdlib_Promise.resi 357,328 357,820 +0.14%
packages/@rescript/belt/src/Belt_SortArray.res 1,468,070 1,470,091 +0.14%
packages/@rescript/runtime/Stdlib_Temporal_Common.res 366,129 366,621 +0.13%
packages/@rescript/runtime/Stdlib_Array.resi 731,946 732,889 +0.13%
packages/@rescript/runtime/JsxDOM.res 2,663,482 2,666,907 +0.13%
packages/@rescript/runtime/JsxEvent.res 1,088,269 1,089,582 +0.12%
packages/@rescript/belt/src/Belt_MutableSet.res 1,923,007 1,925,280 +0.12%
packages/@rescript/runtime/Primitive_hash.res 974,324 975,433 +0.11%
packages/@rescript/runtime/Stdlib_Date.resi 618,660 619,337 +0.11%
packages/@rescript/belt/src/Belt_List.resi 758,182 759,009 +0.11%
packages/@rescript/runtime/Stdlib_Result.res 1,279,680 1,280,972 +0.10%

Instructions

In total: 9,212,082,829 → 9,215,995,461 (+0.04%).

Benchmark Base Head Change
packages/@rescript/runtime/Stdlib_String.resi 29,366,796 29,442,183 +0.26%
packages/@rescript/runtime/Stdlib_Temporal_PlainDate.res 24,006,224 24,063,988 +0.24%
packages/@rescript/runtime/Stdlib_Temporal_ZonedDateTime.res 31,066,805 31,135,846 +0.22%
packages/@rescript/runtime/Stdlib_Math.resi 21,195,718 21,242,234 +0.22%
packages/@rescript/runtime/Stdlib_Temporal_PlainDateTime.res 25,446,158 25,499,858 +0.21%
packages/@rescript/runtime/JsxDOM.res 117,168,844 117,396,712 +0.19%
packages/@rescript/runtime/Stdlib_Date.resi 34,131,005 34,191,100 +0.18%
packages/@rescript/runtime/Stdlib_List.resi 24,539,745 24,582,190 +0.17%
packages/@rescript/runtime/Stdlib_Array.resi 39,978,875 40,045,696 +0.17%
packages/@rescript/belt/src/Belt_Array.resi 33,719,931 33,772,882 +0.16%
5 more changes
Benchmark Base Head Change
packages/@rescript/belt/src/Belt_List.resi 39,005,746 39,055,661 +0.13%
stress/NestedClosures.res 529,264,138 529,884,688 +0.12%
packages/@rescript/runtime/Stdlib_TypedArray.res 41,108,448 41,156,349 +0.12%
packages/@rescript/runtime/JsxDOMStyle.res 95,102,634 95,212,321 +0.12%
parse/RedBlackTreeNoComments.res 14,550,875 14,566,318 +0.11%

Output size

In total: 25,844,591 → 25,846,458 (+0.01%).

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

Pull request 4b6c1e4 compared with base 1140711 in this run.

@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 737b3cf to 6aaa334 Compare November 10, 2026 06:14
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch 3 times, most recently from 0c22d07 to 71f4b13 Compare November 10, 2026 13:40
JonoPrest added a commit that referenced this pull request Nov 10, 2026
Why:
    Id_tbl.find_opened returned the binding and its position as a new
    pair, which find_name and find_all took apart again to build the
    path, so every lookup of a name found in the components of an open
    allocated three words more than before the open tables took the
    cells. In stress/RefsInLoop
    that moved each statement's allocations from 46272 to 46368 bytes, a
    Fibonacci number: with the minor heap the benchmarks run with, the
    objects of the 300 statements in the .cmt then fall on the same slot of
    the marshaller's position table (Fibonacci hashing of addresses, linear
    probing), and marshalling the .cmt took about 40M more instructions
    (+23% under cachegrind), though the file is byte-identical.

find_opened now gives the path and the binding, built from the pair stored
in the table; update, which only Env.copy_types calls, looks the binding up
as before. The bindings found, their order and the outputs are unchanged.

Measured with scripts/compilerBenchmarks.js against the previous commit:
allocated words -0.02% in total, stress/NestedClosures -0.13%, and
stress/RefsInLoop -5.4K words; with perf stat (s=64M as the benchmarks
run) RefsInLoop takes 456.5M instructions, against 515.0M before and 456.1M
for #9192 head.

Review: Does the instruction count of RefsInLoop now depend on luck? Yes,
as before #9194: any change of 96 bytes per statement in this benchmark
can land on a stride that hashes badly, without any work in the compiler.
The fix removes the allocation that moved it there, which is worth having
on its own.

Signed-off-by: Jono Prest <jjprest@gmail.com>
JonoPrest added a commit that referenced this pull request Nov 10, 2026
Why:
    Id_tbl.find_opened returned the binding and its position as a new
    pair, which find_name and find_all took apart again to build the
    path, so every lookup of a name found in the components of an open
    allocated three words more than before the open tables took the
    cells. In stress/RefsInLoop
    that moved each statement's allocations from 46272 to 46368 bytes, a
    Fibonacci number: with the minor heap the benchmarks run with, the
    objects of the 300 statements in the .cmt then fall on the same slot of
    the marshaller's position table (Fibonacci hashing of addresses, linear
    probing), and marshalling the .cmt took about 40M more instructions
    (+23% under cachegrind), though the file is byte-identical.

find_opened now gives the path and the binding, built from the pair stored
in the table; update, which only Env.copy_types calls, looks the binding up
as before. The bindings found, their order and the outputs are unchanged.

Measured with scripts/compilerBenchmarks.js against the previous commit:
allocated words -0.02% in total, stress/NestedClosures -0.13%, and
stress/RefsInLoop -5.4K words; with perf stat (s=64M as the benchmarks
run) RefsInLoop takes 456.5M instructions, against 515.0M before and 456.1M
for #9192 head.

Review: Does the instruction count of RefsInLoop now depend on luck? Yes,
as before #9194: any change of 96 bytes per statement in this benchmark
can land on a stride that hashes badly, without any work in the compiler.
The fix removes the allocation that moved it there, which is worth having
on its own.

Signed-off-by: Jono Prest <jjprest@gmail.com>
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 911b4e0 to 1d62ccc Compare November 10, 2026 14:44
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 1d62ccc to 9017d3f Compare November 10, 2026 16:14
JonoPrest added a commit that referenced this pull request Nov 10, 2026
Why:
    Id_tbl.find_opened returned the binding and its position as a new
    pair, which find_name and find_all took apart again to build the
    path, so every lookup of a name found in the components of an open
    allocated three words more than before the open tables took the
    cells. In stress/RefsInLoop
    that moved each statement's allocations from 46272 to 46368 bytes, a
    Fibonacci number: with the minor heap the benchmarks run with, the
    objects of the 300 statements in the .cmt then fall on the same slot of
    the marshaller's position table (Fibonacci hashing of addresses, linear
    probing), and marshalling the .cmt took about 40M more instructions
    (+23% under cachegrind), though the file is byte-identical.

find_opened now gives the path and the binding, built from the pair stored
in the table; update, which only Env.copy_types calls, looks the binding up
as before. The bindings found, their order and the outputs are unchanged.

Measured with scripts/compilerBenchmarks.js against the previous commit:
allocated words -0.02% in total, stress/NestedClosures -0.13%, and
stress/RefsInLoop -5.4K words; with perf stat (s=64M as the benchmarks
run) RefsInLoop takes 456.5M instructions, against 515.0M before and 456.1M
for #9192 head.

Review: Does the instruction count of RefsInLoop now depend on luck? Yes,
as before #9194: any change of 96 bytes per statement in this benchmark
can land on a stride that hashes badly, without any work in the compiler.
The fix removes the allocation that moved it there, which is worth having
on its own.

Signed-off-by: Jono Prest <jjprest@gmail.com>
JonoPrest added a commit that referenced this pull request Nov 10, 2026
Why:
    Id_tbl.find_opened returned the binding and its position as a new
    pair, which find_name and find_all took apart again to build the
    path, so every lookup of a name found in the components of an open
    allocated three words more than before the open tables took the
    cells. In stress/RefsInLoop
    that moved each statement's allocations from 46272 to 46368 bytes, a
    Fibonacci number: with the minor heap the benchmarks run with, the
    objects of the 300 statements in the .cmt then fall on the same slot of
    the marshaller's position table (Fibonacci hashing of addresses, linear
    probing), and marshalling the .cmt took about 40M more instructions
    (+23% under cachegrind), though the file is byte-identical.

find_opened now gives the path and the binding, built from the pair stored
in the table; update, which only Env.copy_types calls, looks the binding up
as before. The bindings found, their order and the outputs are unchanged.

Measured with scripts/compilerBenchmarks.js against the previous commit:
allocated words -0.02% in total, stress/NestedClosures -0.13%, and
stress/RefsInLoop -5.4K words; with perf stat (s=64M as the benchmarks
run) RefsInLoop takes 456.5M instructions, against 515.0M before and 456.1M
for #9192 head.

Review: Does the instruction count of RefsInLoop now depend on luck? Yes,
as before #9194: any change of 96 bytes per statement in this benchmark
can land on a stride that hashes badly, without any work in the compiler.
The fix removes the allocation that moved it there, which is worth having
on its own.

Signed-off-by: Jono Prest <jjprest@gmail.com>
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 9017d3f to b87ee5c Compare November 10, 2026 16:24
JonoPrest added a commit that referenced this pull request Nov 10, 2026
Why:
    Id_tbl.find_opened returned the binding and its position as a new
    pair, which find_name and find_all took apart again to build the
    path, so every lookup of a name found in the components of an open
    allocated three words more than before the open tables took the
    cells. In stress/RefsInLoop
    that moved each statement's allocations from 46272 to 46368 bytes, a
    Fibonacci number: with the minor heap the benchmarks run with, the
    objects of the 300 statements in the .cmt then fall on the same slot of
    the marshaller's position table (Fibonacci hashing of addresses, linear
    probing), and marshalling the .cmt took about 40M more instructions
    (+23% under cachegrind), though the file is byte-identical.

find_opened now gives the path and the binding, built from the pair stored
in the table; update, which only Env.copy_types calls, looks the binding up
as before. The bindings found, their order and the outputs are unchanged.

Measured with scripts/compilerBenchmarks.js against the previous commit:
allocated words -0.02% in total, stress/NestedClosures -0.13%, and
stress/RefsInLoop -5.4K words; with perf stat (s=64M as the benchmarks
run) RefsInLoop takes 456.5M instructions, against 515.0M before and 456.1M
for #9192 head.

Review: Does the instruction count of RefsInLoop now depend on luck? Yes,
as before #9194: any change of 96 bytes per statement in this benchmark
can land on a stride that hashes badly, without any work in the compiler.
The fix removes the allocation that moved it there, which is worth having
on its own.

Signed-off-by: Jono Prest <jjprest@gmail.com>
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from b87ee5c to 64186ad Compare November 10, 2026 17:24
JonoPrest added a commit that referenced this pull request Nov 10, 2026
Why:
    Id_tbl.find_opened returned the binding and its position as a new
    pair, which find_name and find_all took apart again to build the
    path, so every lookup of a name found in the components of an open
    allocated three words more than before the open tables took the
    cells. In stress/RefsInLoop
    that moved each statement's allocations from 46272 to 46368 bytes, a
    Fibonacci number: with the minor heap the benchmarks run with, the
    objects of the 300 statements in the .cmt then fall on the same slot of
    the marshaller's position table (Fibonacci hashing of addresses, linear
    probing), and marshalling the .cmt took about 40M more instructions
    (+23% under cachegrind), though the file is byte-identical.

find_opened now gives the path and the binding, built from the pair stored
in the table; update, which only Env.copy_types calls, looks the binding up
as before. The bindings found, their order and the outputs are unchanged.

Measured with scripts/compilerBenchmarks.js against the previous commit:
allocated words -0.02% in total, stress/NestedClosures -0.13%, and
stress/RefsInLoop -5.4K words; with perf stat (s=64M as the benchmarks
run) RefsInLoop takes 456.5M instructions, against 515.0M before and 456.1M
for #9192 head.

Review: Does the instruction count of RefsInLoop now depend on luck? Yes,
as before #9194: any change of 96 bytes per statement in this benchmark
can land on a stride that hashes badly, without any work in the compiler.
The fix removes the allocation that moved it there, which is worth having
on its own.

Signed-off-by: Jono Prest <jjprest@gmail.com>
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from 64186ad to f1672f7 Compare November 10, 2026 18:14
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 <jjprest@gmail.com>
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 <jjprest@gmail.com>
Why:
    Id_tbl.find_opened returned the binding and its position as a new
    pair, which find_name and find_all took apart again to build the
    path, so every lookup of a name found in the components of an open
    allocated three words more than before the open tables took the
    cells. In stress/RefsInLoop
    that moved each statement's allocations from 46272 to 46368 bytes, a
    Fibonacci number: with the minor heap the benchmarks run with, the
    objects of the 300 statements in the .cmt then fall on the same slot of
    the marshaller's position table (Fibonacci hashing of addresses, linear
    probing), and marshalling the .cmt took about 40M more instructions
    (+23% under cachegrind), though the file is byte-identical.

find_opened now gives the path and the binding, built from the pair stored
in the table; update, which only Env.copy_types calls, looks the binding up
as before. The bindings found, their order and the outputs are unchanged.

Measured with scripts/compilerBenchmarks.js against the previous commit:
allocated words -0.02% in total, stress/NestedClosures -0.13%, and
stress/RefsInLoop -5.4K words; with perf stat (s=64M as the benchmarks
run) RefsInLoop takes 456.5M instructions, against 515.0M before and 456.1M
for #9192 head.

Review: Does the instruction count of RefsInLoop now depend on luck? Yes,
as before #9194: any change of 96 bytes per statement in this benchmark
can land on a stride that hashes badly, without any work in the compiler.
The fix removes the allocation that moved it there, which is worth having
on its own.

Signed-off-by: Jono Prest <jjprest@gmail.com>
@JonoPrest
JonoPrest force-pushed the jono/defer-dependency-value-copies branch from f1672f7 to 4b6c1e4 Compare November 10, 2026 18:52

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