Skip to content

Copy a dependency's interface only when it is read - #9192

Open
JonoPrest wants to merge 4 commits into
masterfrom
jono/defer-signature-copy
Open

JonoPrest wants to merge 4 commits into
masterfrom
jono/defer-signature-copy

Conversation

@JonoPrest

Copy link
Copy Markdown
Contributor

When a module refers to a dependency, Env.normalize_path checks whether it is a module alias, and that forces the dependency's ps_sig: a full Subst copy of the interface. Almost none of those copies are read afterwards. On a 552 module React app these copies take about 5% of build CPU. With this change 2,921 of them are deferred, and only 10 of those are ever read.

This defers the copy without changing any output:

  • A compilation unit is never an alias, so normalize_path now only loads the .cmi (imports and CRC checks still happen) and reserves the identifier stamps and type ids that the copy would take, by advancing both counters.
  • The first real read (find_module, fold_modules, read_signature) makes the copy with the counters set back to the reserved bases. Every stamp and type id is then the same as with an eager copy, so .cmi, .cmj, JS and .cmt stay byte identical.
  • The number of stamps and ids a copy takes depends on the shape of the type graph, so it is recorded from the first eager copy of the same interface contents, keyed by the CRC written in the .cmi. The first copy of each interface in a process (and a one shot bsc) is still made eagerly.
  • If a copy ever takes a different count than reserved, it is discarded and made again with fresh ids. This has not happened on any project.

It is independent of #8874, which caches reading the .cmi files rather than copying them.

Clean builds, 8 vCPU Linux VM, this PR / master, 5 runs:

project default domains wall / CPU 1 domain wall / CPU
React app, 552 modules 0.91 / 0.91 0.93 / 0.94
backend service, 391 modules ~1.0 / 0.96 0.94 / 0.94
library 0.95 / ~1.0 0.96 / 0.94

Testing:

  • New ounit_deferred_signature_tests.ml copies every runtime and Belt .cmi eagerly and deferred, creating identifiers and type variables between reservations and reading the signatures in reverse order, and compares the marshalled results byte for byte. It also covers interfaces that are reserved but never read, counter restore on exceptions, and the fallback for a wrong count.
  • belt_deferred_signature_copy_test.res covers alias chains, functors, module type of, include and recursive modules over dependency types, and three super_errors fixtures print dependency types through aliases.
  • JS, .cmi, .cmj, .ast, .cmt and .cmti are byte identical to master across three production codebases and their dependencies at 1, 16 and the default number of domains. make test and make test-rewatch pass.

JonoPrest added a commit that referenced this pull request Nov 3, 2026
The entry names #9192 and is filed under polish like the other build
speedups.

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

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Changelog

💅 Polish

  • Build: copy a dependency's interface only when it is read, after the first copy of the same interface in a build, instead of every time a module checks whether the dependency is an alias.

Semantic Diff

4 commits · Full page

Copy a dependency's signature only when it is read

de2e2d6 · JonoPrest · Nov 03, 2026, 08:47 UTC

Why

Env keeps each loaded compilation unit's signature as a lazy copy of the .cmi signature with its binders renamed (Subst.signature). normalize_path forced that copy just to learn that a compilation unit is not a module alias, which it never is; normalize_path is reached for every value reference such as Belt.Array.map, for abstract dependency types in Ctype.try_expand_once, and from Includemod and Printtyp. These copies were about 4-6 % of a clean build's CPU time on real projects (ui: 0.62 s of 13.3 s with one domain).

normalize_path now only looks the unit up (keeping the .cmi load, import and CRC checks) and reserves the stamps and type ids the copy would take, so every later identifier and type node gets the same stamp and id as before. The copy is made with exactly those stamps and ids when the signature is first read (find_module, scrape_alias, fold_modules, read_signature); on a 552-module app 2,921 copies are reserved and 10 are ever made.

How many stamps and ids a copy takes is learnt from the first copy of the same .cmi contents, keyed by the CRC create_cmi wrote, and kept for the process. If a reserved copy ever takes a different number, it is thrown away and made again with fresh stamps and ids, so a wrong count can change ids but never makes two nodes share one.

Review

  • Are the artifacts the same? Yes: stamps and type ids are the same by construction. JS, .cmi, .cmj, .ast and .cmt are byte-identical to master on the 5,398-artifact corpus (default and one domain), and a unit test checks that deferred copies of every runtime and Belt interface marshal to the same bytes as eager ones, with the same stamps and ids created around them.
  • Can the copy differ when it is made later? Only if the .cmi graph changed in between; the type checker only works on copies of it, and closed object types' shared mutability cells, the one part it can change, do not change what is copied.
Read the signatures in reverse order in the deferred copy test

5247378 · JonoPrest · Nov 03, 2026, 08:53 UTC

List.rev_map applies its function from the head, so the test read the signatures in the order they were reserved; it now reads the last one first, so each copy is made with stamps and ids reserved before others.

Test the counter restore and the fallback of deferred signature copies

7d14ed3 · JonoPrest · Nov 03, 2026, 09:02 UTC

The independent review found the env.mli comment for read_signature attached to the new stats function, and the comment and changelog overstating when copies are deferred: only after an earlier copy of the same interface in the process, never for the first one or a one-shot bsc. New unit tests check that Ident.with_current_time puts the counter back when its function raises, and that a deferred copy whose counts are wrong (a forged .cmi CRC) is made again with fresh stamps.

Build on OCaml 5.0 and test unread copies with runtime interfaces

3220de0 · JonoPrest · Nov 03, 2026, 09:23 UTC

Mutex.protect only exists from OCaml 5.1, so the count table locks with Mutex.lock and Fun.protect like Process_child.with_lock. The unread copy test asked for Belt modules, which the coverage job doesn't build; it now uses runtime interfaces only.

@JonoPrest

Copy link
Copy Markdown
Contributor Author

@codex review

@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-03T09:42:47.107983Z 5695c85 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: Something went wrong. Try again later by commenting “@codex review”.

Unknown error
ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: 120bb652f6

ℹ️ 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 95.09202% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.95%. Comparing base (70da3d1) to head (6bfcbc9).

Files with missing lines Patch % Lines
compiler/ml/env.ml 93.44% 4 Missing ⚠️
...ests/ounit_tests/ounit_deferred_signature_tests.ml 95.91% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9192      +/-   ##
==========================================
+ Coverage   81.92%   81.95%   +0.03%     
==========================================
  Files         456      457       +1     
  Lines       65464    65620     +156     
==========================================
+ Hits        53631    53780     +149     
- Misses      11833    11840       +7     
Files with missing lines Coverage Δ
compiler/ext/ident.ml 91.22% <100.00%> (+0.31%) ⬆️
tests/ounit_tests/ounit_tests_main.ml 100.00% <ø> (ø)
compiler/ml/env.ml 80.12% <93.44%> (+0.84%) ⬆️
...ests/ounit_tests/ounit_deferred_signature_tests.ml 95.91% <95.91%> (ø)
🚀 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@9192

@rescript/belt

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

@rescript/darwin-arm64

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

@rescript/linux-arm64

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

@rescript/linux-x64

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

@rescript/runtime

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

@rescript/win32-x64

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

commit: 6bfcbc9

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

@JonoPrest

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 5695c85e05

ℹ️ 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".

@JonoPrest
JonoPrest marked this pull request as ready for review November 3, 2026 09:43
@JonoPrest
JonoPrest requested a review from cknitt November 3, 2026 09:43
@JonoPrest
JonoPrest added this pull request to stack #9195 November 3, 2026 11:38
@JonoPrest
JonoPrest force-pushed the jono/defer-signature-copy branch 5 times, most recently from 7199bfb to 4de59d8 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: 683,553,051 → 680,175,931 (-0.49%).

Benchmark Base Head Change
build/clean 246,759,757 244,315,299 -0.99%
build/shared-edit 75,191,231 74,446,754 -0.99%
packages/@rescript/belt/src/Belt_SetDict.res 1,634,169 1,625,079 -0.56%
packages/@rescript/belt/src/Belt_internalMapInt.res 1,610,677 1,601,947 -0.54%
packages/@rescript/belt/src/Belt_internalMapString.res 1,612,527 1,603,791 -0.54%
packages/@rescript/belt/src/Belt_MutableSet.res 2,121,669 2,110,547 -0.52%
packages/@rescript/belt/src/Belt_MapDict.res 1,875,071 1,865,435 -0.51%
packages/@rescript/belt/src/Belt_SetString.res 1,413,817 1,406,643 -0.51%
packages/@rescript/belt/src/Belt_SetInt.res 1,413,088 1,405,930 -0.51%
packages/@rescript/belt/src/Belt_MutableSetString.res 1,832,096 1,823,516 -0.47%
67 more changes
Benchmark Base Head Change
packages/@rescript/belt/src/Belt_MutableSetInt.res 1,830,853 1,822,279 -0.47%
packages/@rescript/belt/src/Belt_MapInt.res 1,333,449 1,327,212 -0.47%
packages/@rescript/belt/src/Belt_MapString.res 1,333,987 1,327,800 -0.46%
packages/@rescript/belt/src/Belt_Set.res 932,088 927,874 -0.45%
packages/@rescript/belt/src/Belt_HashMapInt.res 1,190,629 1,185,319 -0.45%
packages/@rescript/belt/src/Belt_HashMapString.res 1,191,337 1,186,031 -0.45%
packages/@rescript/belt/src/Belt_HashMap.res 1,362,983 1,357,049 -0.44%
packages/@rescript/belt/src/Belt_MutableMapInt.res 1,291,463 1,285,967 -0.43%
packages/@rescript/belt/src/Belt_MutableMapString.res 1,291,746 1,286,252 -0.43%
packages/@rescript/belt/src/Belt_MutableMap.res 1,459,148 1,453,036 -0.42%
packages/@rescript/belt/src/Belt_HashSet.res 1,092,814 1,088,452 -0.40%
packages/@rescript/belt/src/Belt_HashSetInt.res 932,283 928,583 -0.40%
packages/@rescript/belt/src/Belt_HashSetString.res 932,602 928,904 -0.40%
packages/@rescript/belt/src/Belt_internalSetInt.res 788,501 785,447 -0.39%
packages/@rescript/belt/src/Belt_internalSetString.res 789,694 786,638 -0.39%
packages/@rescript/belt/src/Belt_Map.res 1,073,930 1,069,788 -0.39%
stress/RefsInLoop.res 17,141,783 17,093,326 -0.28%
packages/@rescript/belt/src/Belt_internalBuckets.res 1,076,130 1,073,837 -0.21%
packages/@rescript/belt/src/Belt_internalSetBuckets.res 759,864 758,364 -0.20%
packages/@rescript/runtime/Primitive_hash.res 1,022,797 1,020,783 -0.20%
packages/@rescript/runtime/Stdlib_Iterable.res 98,918 99,090 +0.17%
packages/@rescript/runtime/Stdlib_AsyncIterable.res 98,989 99,161 +0.17%
packages/@rescript/runtime/Stdlib_File.res 95,311 95,471 +0.17%
packages/@rescript/runtime/Primitive_list.resi 91,811 91,963 +0.17%
packages/@rescript/runtime/Primitive_float_extern.res 93,356 93,508 +0.16%
packages/@rescript/runtime/Stdlib_Blob.res 93,474 93,626 +0.16%
packages/@rescript/runtime/Obj.res 98,908 99,068 +0.16%
packages/@rescript/runtime/Stdlib_ArrayBuffer.res 107,226 107,398 +0.16%
packages/@rescript/runtime/Primitive_promise.res 96,873 97,025 +0.16%
packages/@rescript/runtime/Primitive_int_extern.res 97,645 97,797 +0.16%
packages/@rescript/runtime/Primitive_string_extern.res 97,997 98,149 +0.16%
packages/@rescript/runtime/Stdlib_Iterable.resi 98,228 98,380 +0.15%
packages/@rescript/runtime/Stdlib_AsyncIterable.resi 98,375 98,527 +0.15%
packages/@rescript/runtime/Primitive_exceptions.resi 100,194 100,346 +0.15%
packages/@rescript/runtime/Stdlib_TaggedTemplate.res 115,787 115,959 +0.15%
packages/@rescript/runtime/Primitive_hash.resi 102,684 102,836 +0.15%
packages/@rescript/runtime/Primitive_dict.res 102,794 102,946 +0.15%
packages/@rescript/runtime/Primitive_module.resi 103,101 103,253 +0.15%
packages/@rescript/runtime/RescriptTools.res 104,121 104,273 +0.15%
packages/@rescript/runtime/Stdlib_TaggedTemplate.resi 106,559 106,711 +0.14%
packages/@rescript/runtime/Stdlib_ArrayBuffer.resi 110,946 111,098 +0.14%
packages/@rescript/runtime/Primitive_object.resi 117,272 117,432 +0.14%
packages/@rescript/runtime/RescriptTools_ExtractCodeBlocks.res 112,006 112,158 +0.14%
packages/@rescript/runtime/Stdlib_Lazy.resi 112,504 112,656 +0.14%
packages/@rescript/runtime/Primitive_array_extern.res 114,530 114,682 +0.13%
packages/@rescript/runtime/Primitive_object_extern.res 114,734 114,886 +0.13%
packages/@rescript/runtime/Stdlib_Intl_Segments.res 116,632 116,784 +0.13%
packages/@rescript/runtime/Stdlib_Bool.resi 116,764 116,916 +0.13%
packages/@rescript/runtime/Dom_storage2.res 119,942 120,094 +0.13%
packages/@rescript/runtime/Primitive_option.resi 122,540 122,694 +0.13%
packages/@rescript/runtime/Jsx.res 122,098 122,250 +0.12%
packages/@rescript/runtime/Stdlib_JsExn.resi 123,052 123,204 +0.12%
packages/@rescript/runtime/Stdlib_WeakSet.res 123,164 123,316 +0.12%
packages/@rescript/runtime/Stdlib_Global.res 141,938 142,110 +0.12%
packages/@rescript/runtime/Stdlib_Symbol.res 144,612 144,784 +0.12%
packages/@rescript/runtime/Stdlib_Bool.res 147,200 147,372 +0.12%
packages/@rescript/runtime/Stdlib_Type.resi 135,887 136,039 +0.11%
packages/@rescript/runtime/Stdlib_WeakMap.res 136,058 136,210 +0.11%
packages/@rescript/runtime/Stdlib_JsExn.res 177,282 177,474 +0.11%
packages/@rescript/runtime/Stdlib_Temporal_Now.res 143,171 143,323 +0.11%
packages/@rescript/runtime/Stdlib_RegExp.res 164,130 164,302 +0.10%
packages/@rescript/belt/src/Belt_Float.res 165,952 166,124 +0.10%
packages/@rescript/belt/src/Belt_Int.res 166,621 166,793 +0.10%
packages/@rescript/runtime/Primitive_bool.res 149,417 149,569 +0.10%
packages/@rescript/runtime/Primitive_float.res 149,645 149,797 +0.10%
packages/@rescript/belt/src/Belt_Int.resi 149,758 149,910 +0.10%
packages/@rescript/belt/src/Belt_Float.resi 151,234 151,386 +0.10%

Instructions

In total: 9,664,671,698 → 9,664,627,235 (±0.00%).

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

Output size

In total: 25,569,598 → 25,570,910 (+0.01%).

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

Pull request 3220de0 compared with base b82d944 in this run.

@JonoPrest
JonoPrest force-pushed the jono/defer-signature-copy branch 4 times, most recently from 93f5ec1 to 37bc835 Compare November 9, 2026 13:05
@JonoPrest
JonoPrest force-pushed the jono/defer-signature-copy branch from 37bc835 to 3220de0 Compare November 9, 2026 16:43
Why:
    Env keeps each loaded compilation unit's signature as a lazy copy of the
    .cmi signature with its binders renamed (Subst.signature). normalize_path
    forced that copy just to learn that a compilation unit is not a module
    alias, which it never is; normalize_path is reached for every value
    reference such as Belt.Array.map, for abstract dependency types in
    Ctype.try_expand_once, and from Includemod and Printtyp. These copies were
    about 4-6 % of a clean build's CPU time on real projects (ui: 0.62 s of
    13.3 s with one domain).

normalize_path now only looks the unit up (keeping the .cmi load, import and
CRC checks) and reserves the stamps and type ids the copy would take, so
every later identifier and type node gets the same stamp and id as before.
The copy is made with exactly those stamps and ids when the signature is
first read (find_module, scrape_alias, fold_modules, read_signature); on a
552-module app 2,921 copies are reserved and 10 are ever made.

How many stamps and ids a copy takes is learnt from the first copy of the
same .cmi contents, keyed by the CRC create_cmi wrote, and kept for the
process. If a reserved copy ever takes a different number, it is thrown away
and made again with fresh stamps and ids, so a wrong count can change ids but
never makes two nodes share one.

Review: Are the artifacts the same? Yes: stamps and type ids are the same by
construction. JS, .cmi, .cmj, .ast and .cmt are byte-identical to master on
the 5,398-artifact corpus (default and one domain), and a unit test checks
that deferred copies of every runtime and Belt interface marshal to the same
bytes as eager ones, with the same stamps and ids created around them.
Review: Can the copy differ when it is made later? Only if the .cmi graph
changed in between; the type checker only works on copies of it, and closed
object types' shared mutability cells, the one part it can change, do not
change what is copied.

Signed-off-by: Jono Prest <[email protected]>
List.rev_map applies its function from the head, so the test read the
signatures in the order they were reserved; it now reads the last one
first, so each copy is made with stamps and ids reserved before others.

Signed-off-by: Jono Prest <[email protected]>
The independent review found the env.mli comment for read_signature
attached to the new stats function, and the comment and changelog
overstating when copies are deferred: only after an earlier copy of the
same interface in the process, never for the first one or a one-shot bsc.
New unit tests check that Ident.with_current_time puts the counter back
when its function raises, and that a deferred copy whose counts are wrong
(a forged .cmi CRC) is made again with fresh stamps.

Signed-off-by: Jono Prest <[email protected]>
Mutex.protect only exists from OCaml 5.1, so the count table locks with
Mutex.lock and Fun.protect like Process_child.with_lock. The unread copy
test asked for Belt modules, which the coverage job doesn't build; it now
uses runtime interfaces only.

Signed-off-by: Jono Prest <[email protected]>
@JonoPrest
JonoPrest force-pushed the jono/defer-signature-copy branch from 3220de0 to 6bfcbc9 Compare November 9, 2026 22:04
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

Changelog

💅 Polish

  • Build: copy a dependency's interface only when it is read, after the first copy of the same interface in a build, instead of every time a module checks whether the dependency is an alias.

Semantic Diff

4 commits · Full page

Copy a dependency's signature only when it is read

cc2ee4c · JonoPrest · Nov 03, 2026, 08:47 UTC

Why

Env keeps each loaded compilation unit's signature as a lazy copy of the .cmi signature with its binders renamed (Subst.signature). normalize_path forced that copy just to learn that a compilation unit is not a module alias, which it never is; normalize_path is reached for every value reference such as Belt.Array.map, for abstract dependency types in Ctype.try_expand_once, and from Includemod and Printtyp. These copies were about 4-6 % of a clean build's CPU time on real projects (ui: 0.62 s of 13.3 s with one domain).

normalize_path now only looks the unit up (keeping the .cmi load, import and CRC checks) and reserves the stamps and type ids the copy would take, so every later identifier and type node gets the same stamp and id as before. The copy is made with exactly those stamps and ids when the signature is first read (find_module, scrape_alias, fold_modules, read_signature); on a 552-module app 2,921 copies are reserved and 10 are ever made.

How many stamps and ids a copy takes is learnt from the first copy of the same .cmi contents, keyed by the CRC create_cmi wrote, and kept for the process. If a reserved copy ever takes a different number, it is thrown away and made again with fresh stamps and ids, so a wrong count can change ids but never makes two nodes share one.

Review

  • Are the artifacts the same? Yes: stamps and type ids are the same by construction. JS, .cmi, .cmj, .ast and .cmt are byte-identical to master on the 5,398-artifact corpus (default and one domain), and a unit test checks that deferred copies of every runtime and Belt interface marshal to the same bytes as eager ones, with the same stamps and ids created around them.
  • Can the copy differ when it is made later? Only if the .cmi graph changed in between; the type checker only works on copies of it, and closed object types' shared mutability cells, the one part it can change, do not change what is copied.
Read the signatures in reverse order in the deferred copy test

04a12ed · JonoPrest · Nov 03, 2026, 08:53 UTC

List.rev_map applies its function from the head, so the test read the signatures in the order they were reserved; it now reads the last one first, so each copy is made with stamps and ids reserved before others.

Test the counter restore and the fallback of deferred signature copies

d1dd523 · JonoPrest · Nov 03, 2026, 09:02 UTC

The independent review found the env.mli comment for read_signature attached to the new stats function, and the comment and changelog overstating when copies are deferred: only after an earlier copy of the same interface in the process, never for the first one or a one-shot bsc. New unit tests check that Ident.with_current_time puts the counter back when its function raises, and that a deferred copy whose counts are wrong (a forged .cmi CRC) is made again with fresh stamps.

Build on OCaml 5.0 and test unread copies with runtime interfaces

6bfcbc9 · JonoPrest · Nov 03, 2026, 09:23 UTC

Mutex.protect only exists from OCaml 5.1, so the count table locks with Mutex.lock and Fun.protect like Process_child.with_lock. The unread copy test asked for Belt modules, which the coverage job doesn't build; it now uses runtime interfaces only.

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

✅ Compiler benchmarks

Allocated words

In total: 683,548,644 → 680,171,541 (-0.49%).

Benchmark Base Head Change
build/clean 246,756,417 244,311,996 -0.99%
build/shared-edit 75,190,324 74,445,843 -0.99%
packages/@rescript/belt/src/Belt_SetDict.res 1,634,169 1,625,079 -0.56%
packages/@rescript/belt/src/Belt_internalMapInt.res 1,610,677 1,601,947 -0.54%
packages/@rescript/belt/src/Belt_internalMapString.res 1,612,527 1,603,791 -0.54%
packages/@rescript/belt/src/Belt_MutableSet.res 2,121,669 2,110,547 -0.52%
packages/@rescript/belt/src/Belt_MapDict.res 1,875,071 1,865,435 -0.51%
packages/@rescript/belt/src/Belt_SetString.res 1,413,817 1,406,643 -0.51%
packages/@rescript/belt/src/Belt_SetInt.res 1,413,088 1,405,930 -0.51%
packages/@rescript/belt/src/Belt_MutableSetString.res 1,832,096 1,823,516 -0.47%
67 more changes
Benchmark Base Head Change
packages/@rescript/belt/src/Belt_MutableSetInt.res 1,830,853 1,822,279 -0.47%
packages/@rescript/belt/src/Belt_MapInt.res 1,333,449 1,327,212 -0.47%
packages/@rescript/belt/src/Belt_MapString.res 1,333,987 1,327,800 -0.46%
packages/@rescript/belt/src/Belt_Set.res 932,088 927,874 -0.45%
packages/@rescript/belt/src/Belt_HashMapInt.res 1,190,629 1,185,319 -0.45%
packages/@rescript/belt/src/Belt_HashMapString.res 1,191,337 1,186,031 -0.45%
packages/@rescript/belt/src/Belt_HashMap.res 1,362,983 1,357,049 -0.44%
packages/@rescript/belt/src/Belt_MutableMapInt.res 1,291,463 1,285,967 -0.43%
packages/@rescript/belt/src/Belt_MutableMapString.res 1,291,746 1,286,252 -0.43%
packages/@rescript/belt/src/Belt_MutableMap.res 1,459,148 1,453,036 -0.42%
packages/@rescript/belt/src/Belt_HashSet.res 1,092,814 1,088,452 -0.40%
packages/@rescript/belt/src/Belt_HashSetInt.res 932,283 928,583 -0.40%
packages/@rescript/belt/src/Belt_HashSetString.res 932,602 928,904 -0.40%
packages/@rescript/belt/src/Belt_internalSetInt.res 788,501 785,447 -0.39%
packages/@rescript/belt/src/Belt_internalSetString.res 789,694 786,638 -0.39%
packages/@rescript/belt/src/Belt_Map.res 1,073,930 1,069,788 -0.39%
stress/RefsInLoop.res 17,141,783 17,093,326 -0.28%
packages/@rescript/belt/src/Belt_internalBuckets.res 1,076,130 1,073,837 -0.21%
packages/@rescript/belt/src/Belt_internalSetBuckets.res 759,864 758,364 -0.20%
packages/@rescript/runtime/Primitive_hash.res 1,022,797 1,020,783 -0.20%
packages/@rescript/runtime/Stdlib_Iterable.res 98,918 99,090 +0.17%
packages/@rescript/runtime/Stdlib_AsyncIterable.res 98,989 99,161 +0.17%
packages/@rescript/runtime/Stdlib_File.res 95,311 95,471 +0.17%
packages/@rescript/runtime/Primitive_list.resi 91,811 91,963 +0.17%
packages/@rescript/runtime/Primitive_float_extern.res 93,356 93,508 +0.16%
packages/@rescript/runtime/Stdlib_Blob.res 93,474 93,626 +0.16%
packages/@rescript/runtime/Obj.res 98,908 99,068 +0.16%
packages/@rescript/runtime/Stdlib_ArrayBuffer.res 107,226 107,398 +0.16%
packages/@rescript/runtime/Primitive_promise.res 96,873 97,025 +0.16%
packages/@rescript/runtime/Primitive_int_extern.res 97,645 97,797 +0.16%
packages/@rescript/runtime/Primitive_string_extern.res 97,997 98,149 +0.16%
packages/@rescript/runtime/Stdlib_Iterable.resi 98,228 98,380 +0.15%
packages/@rescript/runtime/Stdlib_AsyncIterable.resi 98,375 98,527 +0.15%
packages/@rescript/runtime/Primitive_exceptions.resi 100,194 100,346 +0.15%
packages/@rescript/runtime/Stdlib_TaggedTemplate.res 115,787 115,959 +0.15%
packages/@rescript/runtime/Primitive_hash.resi 102,684 102,836 +0.15%
packages/@rescript/runtime/Primitive_dict.res 102,794 102,946 +0.15%
packages/@rescript/runtime/Primitive_module.resi 103,101 103,253 +0.15%
packages/@rescript/runtime/RescriptTools.res 104,121 104,273 +0.15%
packages/@rescript/runtime/Stdlib_TaggedTemplate.resi 106,559 106,711 +0.14%
packages/@rescript/runtime/Stdlib_ArrayBuffer.resi 110,946 111,098 +0.14%
packages/@rescript/runtime/Primitive_object.resi 117,272 117,432 +0.14%
packages/@rescript/runtime/RescriptTools_ExtractCodeBlocks.res 112,006 112,158 +0.14%
packages/@rescript/runtime/Stdlib_Lazy.resi 112,504 112,656 +0.14%
packages/@rescript/runtime/Primitive_array_extern.res 114,530 114,682 +0.13%
packages/@rescript/runtime/Primitive_object_extern.res 114,734 114,886 +0.13%
packages/@rescript/runtime/Stdlib_Intl_Segments.res 116,632 116,784 +0.13%
packages/@rescript/runtime/Stdlib_Bool.resi 116,764 116,916 +0.13%
packages/@rescript/runtime/Dom_storage2.res 119,942 120,094 +0.13%
packages/@rescript/runtime/Primitive_option.resi 122,540 122,694 +0.13%
packages/@rescript/runtime/Jsx.res 122,098 122,250 +0.12%
packages/@rescript/runtime/Stdlib_JsExn.resi 123,052 123,204 +0.12%
packages/@rescript/runtime/Stdlib_WeakSet.res 123,164 123,316 +0.12%
packages/@rescript/runtime/Stdlib_Global.res 141,938 142,110 +0.12%
packages/@rescript/runtime/Stdlib_Symbol.res 144,612 144,784 +0.12%
packages/@rescript/runtime/Stdlib_Bool.res 147,200 147,372 +0.12%
packages/@rescript/runtime/Stdlib_Type.resi 135,887 136,039 +0.11%
packages/@rescript/runtime/Stdlib_WeakMap.res 136,058 136,210 +0.11%
packages/@rescript/runtime/Stdlib_JsExn.res 177,282 177,474 +0.11%
packages/@rescript/runtime/Stdlib_Temporal_Now.res 143,171 143,323 +0.11%
packages/@rescript/runtime/Stdlib_RegExp.res 164,130 164,302 +0.10%
packages/@rescript/belt/src/Belt_Float.res 165,952 166,124 +0.10%
packages/@rescript/belt/src/Belt_Int.res 166,621 166,793 +0.10%
packages/@rescript/runtime/Primitive_bool.res 149,417 149,569 +0.10%
packages/@rescript/runtime/Primitive_float.res 149,645 149,797 +0.10%
packages/@rescript/belt/src/Belt_Int.resi 149,758 149,910 +0.10%
packages/@rescript/belt/src/Belt_Float.resi 151,234 151,386 +0.10%

Instructions

In total: 9,664,671,698 → 9,664,627,235 (±0.00%).

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

Output size

In total: 25,569,598 → 25,570,910 (+0.01%).

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

Pull request 6bfcbc9 compared with base 70da3d1 in this run.

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

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