Skip to content

[hl] Fix Out of memory on cyclic anonymous structures#12972

Open
kLabz wants to merge 3 commits into
developmentfrom
unit-tests-vs-oom-v2
Open

[hl] Fix Out of memory on cyclic anonymous structures#12972
kLabz wants to merge 3 commits into
developmentfrom
unit-tests-vs-oom-v2

Conversation

@kLabz

@kLabz kLabz commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

2nd take for #12955 which we had to revert because of poor genhl perf


Recursive anonymous structures (e.g. typedef T = { next : T } or { make<S>(v:S):T }) build tanon/HVirtual values that are cyclic in memory. Several genhl/hlcode/hl2c lookups compared these with OCaml's polymorphic compare, which loops forever on two distinct cyclic values (growing the compare stack until Out of memory). Compiling the hl unit suite twice through the compilation server reliably triggered it; #9662 and #12239 are deterministic direct-compile repros.

Make the type comparisons cycle-safe without slowing genhl down:

  • anons_cache: key a Hashtbl by a cheap, cycle-free hash of the anon field names (xor of name hashes, order-independent) and resolve buckets with a cycle-safe structural tanon_compare (De Bruijn levels break the recursion). This both removes the loop and is at least as fast as the previous tanon-keyed balanced map (~one structural comparison per lookup).
  • ttype_compare: cycle-safe structural order on ttype (De Bruijn levels on HVirtual); non-virtual heads short-circuit to polymorphic compare so plain register types pay nothing. Used by mallocs, gather_types, cached_tuples, method_wrappers, pinterfaces and the hl2c maps (defined_types, type_module, contexts, types_descs) which loop on cyclic virtuals too.
  • tsame: coinductive cycle guard.

Behaviour is unchanged for non-cyclic types, so generated bytecode is equivalent; the hl unit suite differs only in the canonical form of one recursive virtual (see the updated HlCodeTests.testTreeA).

Closes #9662
Closes #12256
Closes #12239

Recursive anonymous structures (e.g. `typedef T = { next : T }` or
`{ make<S>(v:S):T }`) build tanon/HVirtual values that are cyclic in memory.
Several genhl/hlcode/hl2c lookups compared these with OCaml's polymorphic
compare, which loops forever on two *distinct* cyclic values (growing the
compare stack until Out of memory). Compiling the hl unit suite twice through
the compilation server reliably triggered it; #9662 and #12239 are
deterministic direct-compile repros.

Make the type comparisons cycle-safe without slowing genhl down:

- anons_cache: key a Hashtbl by a cheap, cycle-free hash of the anon field
  names (xor of name hashes, order-independent) and resolve buckets with a
  cycle-safe structural tanon_compare (De Bruijn levels break the recursion).
  This both removes the loop and is at least as fast as the previous
  tanon-keyed balanced map (~one structural comparison per lookup).
- ttype_compare: cycle-safe structural order on ttype (De Bruijn levels on
  HVirtual); non-virtual heads short-circuit to polymorphic compare so plain
  register types pay nothing. Used by mallocs, gather_types, cached_tuples,
  method_wrappers, pinterfaces and the hl2c maps (defined_types, type_module,
  contexts, types_descs) which loop on cyclic virtuals too.
- tsame: coinductive cycle guard.

Behaviour is unchanged for non-cyclic types, so generated bytecode is
equivalent; the hl unit suite differs only in the canonical form of one
recursive virtual (see the updated HlCodeTests.testTreeA).

Closes #9662, #12239
@kLabz kLabz requested a review from yuxiaomao June 29, 2026 17:37
… types

The cyclic-anonymous-structure fix made ttype_cmp cycle-safe (seen/depth) for virtual-bearing types, but
ttype_compare still routed non-virtual heads -- HObj/HStruct/HEnum -- to OCaml's polymorphic `compare`, which
has NO cycle detection. That is safe for current codegen only because named protos and tuples are shared
singletons, so polymorphic compare short-circuits on physical equality and never walks a cycle. It is a latent
footgun though: comparing two physically-distinct instances of a cyclic named type or tuple would infinite-loop
-> Out_of_memory.

Generalize the fix so the whole comparator is cycle-safe: route HObj/HStruct/HEnum through ttype_cmp -- named
types compared by path (pname/ename, their identity), anonymous tuples (HEnum ename="") structurally via the
existing cycle-safe recursion. Polymorphic compare is now used only for flat leaf kinds that cannot loop. Drop
the now-unused is_virtual_bearing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant