Skip to content

Fix llvmize CI: promote test outputs + use an LLVM release with llvm-project#37#1

Open
julesjacobs wants to merge 13 commits into
NickBarnes:frametable-rearrangefrom
julesjacobs:frametable-rearrange-ci-fixes
Open

Fix llvmize CI: promote test outputs + use an LLVM release with llvm-project#37#1
julesjacobs wants to merge 13 commits into
NickBarnes:frametable-rearrangefrom
julesjacobs:frametable-rearrange-ci-fixes

Conversation

@julesjacobs

@julesjacobs julesjacobs commented Jul 6, 2026

Copy link
Copy Markdown

Stacked on frametable-rearrange (oxcaml#6399) — fixes the frame-pointers llvmize-tests (x86_64-linux) CI job, which currently fails in two ways:

  1. Stale expected outputs: id_fn.output and dls_get.output were missing the new oxcaml_short_frametables module flag that llvmize now emits (all the *_ir.output files were already updated). Promoted.

  2. SEGVs in all tests that execute LLVM-compiled code: CI downloads the oxcaml-llvmize-16.0.6-minus3 LLVM release, which predates Add oxcaml_short_frametables code for compatibiility with short frametables ocaml-flambda/llvm-project#37 — so LLVM emits classic pointer-aligned frame descriptors while the new runtime walks packed descriptors with delta bytes, and misparses the table.

For (2), this points the llvmize job at a temporary release built from ocaml-flambda/llvm-project#37's head (commit 8c50b62e33, tag oxcaml-llvmize-16.0.6-minus3-pr37 on my fork; clang/llc, Release, X86, built on glibc 2.28). Once llvm-project#37 is merged and an official release is minted, drop the oxcaml_llvm_repo line and bump oxcaml_llvm_tag.

Verification

Built LLVM from llvm-project#37 and this branch locally; make runtest-llvmize with that clang: the six SEGVs (alloc, exn, switch, csel, extcalls, data_decl) are gone and the whole suite passes after the two promotions. Also checked a linked test binary: the LLVM-emitted camlAlloc__frametable contains escape-byte (0x00) descriptors, byte-packed with no padding, matching caml_decode_frame_descr exactly.

🤖 Generated with Claude Code

NickBarnes and others added 13 commits July 3, 2026 13:46
Add an Xmeasure_frametables GC tweak (OCAMLRUNPARAM=Xmeasure_frametables=1)
that walks the frametables registered in each batch and reports descriptor,
header and debuginfo sizes together with register/slot statistics, including
a per-register usage count.
The runtime can register frametables lazily (e.g. for dynamically linked
units), so OCAMLRUNPARAM=Xmeasure_frametables only sees those registered so
far. Emit a per-unit pointer into a linker-collected caml_all_frametables
section (ELF only) and add an Xmeasure_all_frametables tweak that walks every
frametable linked into the executable, registered or not, reading only static
data. The hand-written runtime frametable contributes itself to the section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frame tables will soon be packed without inter-descriptor alignment, so the
runtime must not assume frame descriptors are aligned. Treat a [frame_descr *]
as an opaque byte pointer and read each field at an explicit byte offset via
new caml_read_unaligned_* helpers, instead of through the frame_descr /
frame_descr_long C structs (which are removed). Forming an aligned-typed
pointer to unaligned data would be undefined behaviour.

No on-disk format change yet: descriptors are still emitted aligned; only the
way the runtime reads them changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Now that every runtime read of a frame descriptor is alignment-agnostic, stop
aligning the frame tables: the compiler no longer emits inter-descriptor or
internal padding (emitaux), the hand-written runtime frame table drops its
.align directives (amd64.S), and the runtime frame-table walk no longer rounds
up to 32-bit/word boundaries. Backtrace slots are shift-encoded (the descriptor
address is stored shifted, with the low bits used as a tag) so they no longer
rely on descriptors being aligned.

This packs the descriptors: on ocamlopt.opt the average descriptor shrinks from
21.8 to 18.8 bytes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The debuginfo filename and defname strings were emitted inline, once per
compilation unit. Split the defname out of the name_info / name_and_loc_info
structs into a [defname_offs] field (alongside the existing [filename_offs]),
and emit both the filename and defname strings into a mergeable string section
(SHF_MERGE|SHF_STRINGS, ".rodata.str1.1") so the linker deduplicates identical
strings across all units. The name structs stay in the frame table section,
referencing the strings by signed 32-bit offset.

Because the strings now live in a shared, deduplicated section, they can no
longer be attributed to a single frametable, so the measurement no longer
counts them: caml_debuginfo_measure brackets only the name structs and counts
records (each two 32-bit words); the frametable measurement reports
records*8 + struct-span as the debuginfo size.

On ocamlopt.opt this drops the measured debuginfo from 8.50 to 6.84 MiB and,
in the linked executable, deduplicates the physical strings across units.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The compact frame-descriptor format will store each return address as a delta
from the previous one, which requires the descriptors in a frame table to be
ordered by increasing return address. Record them in that order: call frames
are recorded inline as they are emitted, and allocation/poll frames are
recorded when their out-of-line GC stub is emitted (where the return-address
label is about to be defined) rather than at the allocation site. The frame
descriptor list is built by prepending, so emit_frames reverses it.

Done for both AMD64 and ARM64.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a denser per-descriptor encoding, chosen by the assembler per descriptor:

  * A leading ULEB128 byte sequence holds the return-address delta from the
    previous descriptor (requiring the increasing-address ordering established
    earlier). A leading 0 byte (FRAME_DELTA_ESCAPE) means "escape": the bytes
    that follow are a descriptor in the existing normal/long format.
  * A short descriptor stores a 6-bit frame size (in 16-byte units) plus flags,
    separate num_allocs/num_live bytes, 4-bit alloc-size nibbles, a 1-byte
    hot-register bitmap over the eight commonest GC-root registers, and live
    stack-slot word offsets. Descriptors that do not fit (large/odd frame size,
    cold register, too many live slots, first-in-table, text-section boundary,
    ...) escape to the normal/long format.

The runtime gains caml_decode_frame_descr to decode either form, and the GC
stack scan, backtrace, signal handling and the frametable measurement all go
through it. The hash table is keyed on {retaddr, fd}.

The format is gated off (every descriptor escapes) where the delta cannot be
emitted: the internal assembler / JIT (which cannot resolve the variable-length
delta directive) and ARM64 (not yet ported), via
Emitaux.disable_short_descriptors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Classify each escaped (non-short) descriptor by why it could not use the
short format, mirroring short_encoding in backend/emitaux.ml (same checks,
same order). This separates descriptors that escaped only for a positional
reason (first-in-frametable, or a function / text-section boundary; their
content is short-compatible) from genuine non-fits (cold register, oversized
or misaligned frame, too many live slots, an allocation size that overflows a
4-bit nibble, ...). Reported as a per-reason histogram alongside the
short/medium/long kind counts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The short-frametable format needs LLVM's OxCamlGCPrinter to emit the
escape delta byte and drop descriptor padding
(ocaml-flambda/llvm-project#37); the oxcaml-llvmize-16.0.6-minus3
release predates that, so the llvmize tests segfault.

Temporarily point the llvmize job at a personal-fork release built
from #37's head. Once #37 is merged and an official release exists,
drop oxcaml_llvm_repo and bump oxcaml_llvm_tag instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@NickBarnes
NickBarnes force-pushed the frametable-rearrange branch 7 times, most recently from 696cb17 to 1992279 Compare July 14, 2026 08:53
@NickBarnes
NickBarnes force-pushed the frametable-rearrange branch 3 times, most recently from 9b03b5a to 244fb79 Compare July 15, 2026 13:58
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.

2 participants