Skip to content

Fix native control-flow and symbol miscompiles and complete the fuzz campaign inventory - #28

Merged
Leitwolf11 merged 6 commits into
mainfrom
fix/deep-fuzz-campaigns
Oct 2, 2026
Merged

Leitwolf11 merged 6 commits into
mainfrom
fix/deep-fuzz-campaigns

Conversation

@Leitwolf11

@Leitwolf11 Leitwolf11 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Compiler defects fixed

All of these were valid programs that the frontend accepted and the native pipeline then miscompiled or rejected.

  • Loop control flow in the native Core-to-CorePrep adapter. A for update region fell through to its own entry
    instead of the condition, so the loop never terminated. A while condition was folded into the incoming block, so
    each back-edge re-ran the statements before the loop. The canonicalizing comparison of a numeric for condition
    was dropped.
  • Short-circuit operators. && and || were lowered as eager two-operand instructions; a guarded division
    trapped and a guarded recursion overflowed the stack. They are now control flow, as in the Haskell lowering.
  • Module-wide symbol allocation. Generated temporaries were numbered per function and reused another function's
    identity. Found by the hosted source fuzz campaign.
  • Reserved LLVM prefix. A function in a namespace named llvm produced a symbol in LLVM's intrinsic namespace
    and failed module verification. Found by the hosted source fuzz campaign.
  • Windows JIT section layout. A hosted AddressSanitizer run aborted with an IMAGE_REL_AMD64_ADDR32NB layout
    error. Windows ORC sessions now link each object into one contiguous reservation. This failure was not reproduced
    locally; the change removes its cause and the evidence for it is indirect.

Each fix except the last has a failing-first regression test: exact CorePrep edge assertions in
Compiler/Core/Tests and executable tests in Compiler/Backend/LLVM/Tests that run both optimizer settings against
values computed by host code.

Fuzzing and tooling

  • Source and differential campaigns are separate; the differential oracle gains while and guarded-recursion shapes
    and the smoke sweeps every shape across trip counts 0 through 11.
  • New component-owned libFuzzer targets: CLI, project registry, REPL and AARC ownership, plus an HPC-guided Haskell
    frontend engine and a ThreadSanitizer campaign (fuzz-thread) for threaded targets on macOS and native Linux.
  • Campaign reports require measured execution and RSS counters. The helper terminates the whole process tree when a
    smoke, libFuzzer or HPC process exceeds its watchdog.
  • Outside CI the helper uses a persistent Bazel disk cache; a repeated local campaign dropped from 910 to 436 seconds
    with unchanged or higher executed-input counts. Concurrent targets are off by default because they reduced executed
    inputs on a two-core host.

Verification

  • Hosted CI on the final commit: all required checks passed on Windows, macOS, Ubuntu and Fedora, including
    ASan/UBSan suites, the bounded fuzz campaigns and the ThreadSanitizer ownership campaign.
  • Local (Windows): 21 native suites, the same suites under ASan/UBSan, all Haskell test suites, verify-helpers,
    verify-docs, and a bounded 9-target libFuzzer plus 3-stage HPC campaign.

Known limits

  • A continue inside a for update region still targets the update entry in both lowerings. The frontend cannot
    produce it; the verifier does not reject it.
  • Bounded campaigns are evidence about their inputs and duration, not proofs. The 900-second stress campaign was not
    run locally.
  • Automatic parity checking between the Haskell and native CorePrep lowerings is in the follow-up pull request.

Leitwolf11 and others added 6 commits September 30, 2026 15:59
…ied frontend input across optimizer modes; require measured execution and RSS evidence in campaign reports; strengthen deterministic oracle smoke tests and document sanitizer boundaries
… fuzz campaign inventory

The native Core-to-CorePrep adapter built loop and logical-operator control
flow that diverged from the Haskell CorePrep lowering:

- a for-loop update region fell through to its own entry instead of the
  condition, so every for loop that reached its update never terminated;
- a while condition was folded into the incoming block, so each back-edge
  re-executed the statements preceding the loop, including its initializers;
- the canonicalizing comparison of a numeric for condition was dropped,
  which made valid programs fail CorePrep verification;
- && and || were lowered as eager two-operand instructions, so a guarded
  division trapped and a guarded recursion overflowed the stack.

The adapter now threads one block cursor through expressions and statements.
Loop headers are dedicated blocks, the fallthrough successor of a loop region
is separate from its continue target, and short-circuit operators become a
branch with an initialized Boolean result slot, matching the Haskell lowering.

Regression coverage asserts exact CorePrep edges for for, while, do/while,
nested loops and short-circuit operators, and executes each form through
CorePrep, Xpp, Xmm and LLVM with both native optimizer settings against values
computed by host code. One existing test that pinned the eager LogicalAnd
shape now checks the per-operand canonical comparisons in the lazy shape.

The differential oracle gains while and guarded-recursion shapes, and the
source smoke sweeps every shape across trip counts 0 through 11.

Fuzzing: add component-owned CLI, project registry, REPL and AARC ownership
libFuzzer targets with versioned seed corpora, a bounded project registry
decoder entry point with its tests, and an HPC-guided Haskell frontend engine
with feedback and mutation policy tests. The developer helper shares one
target inventory, terminates the whole process tree when a smoke, libFuzzer
or HPC process exceeds its watchdog, isolates the HPC tick file per stage,
reads the HPC report from the engine's report file, and adds fuzz-thread, a
separate ThreadSanitizer campaign for threaded targets that fails on hosts
without a ThreadSanitizer runtime.

CI runs the ThreadSanitizer ownership campaign on macOS and native Ubuntu and
the fuzz feedback tests with the Haskell layers. The fuzzing, Core IR, testing
and developer recipe documents describe the verified inventory, limits and
remaining coverage boundaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…idence

Fix two failures the first hosted run of the new fuzz inventory exposed:

- OwnershipFuzzer used std::jthread, which Apple libc++ does not provide.
  It now uses std::thread and joins every worker before the destructor
  oracle runs.
- The process-tree watchdog test reported a surviving descendant inside the
  Fedora container. The watchdog had killed it; the orphan stayed a zombie
  because the container's first process never reaped it, and signal 0 still
  succeeds for a zombie. The Unix liveness probe now treats a zombie or dead
  procfs state as terminated.

Campaign scheduling: targets and HPC stages run through one bounded
scheduler. Concurrency is not free evidence: on a two-core, four-thread host
two concurrent targets executed 40 to 90 percent fewer inputs each, so the
default is one job per four logical processors, at most four, which keeps
such hosts and four-vCPU runners sequential. VXS_FUZZ_JOBS overrides it,
targets with a 4096 MiB RSS limit never overlap, and the ThreadSanitizer
campaign always runs one target at a time. Budgets, limits, timeouts and
watchdogs are identical at every job count, the report keeps inventory order,
and the earliest failing target is reported regardless of scheduling.

Build time: outside CI the helper adds a persistent content-addressed Bazel
disk cache, limited to 8 GiB, to its build invocations. The plain, sanitizer
and fuzz configurations share one output tree, and every switch previously
recompiled all owned translation units. Measured on the same sources, a
bounded campaign dropped from 910 to 436 seconds with unchanged or higher
executed-input counts. VXS_BAZEL_DISK_CACHE selects another absolute
directory or disables the cache.

The fuzzing guide documents the job policy, the measurement behind it and
the cache.
…aign

Both inputs are valid programs that passed Core verification and were then
rejected inside the native pipeline.

Module-wide symbol allocation: the Core-to-CorePrep adapter numbered
generated temporaries from the highest identity of the function being
lowered and lifted closure names from a separate counter. Identities are
unique across a module, so a temporary of one function reused another
function's identity and CorePrep verification reported one identity with
conflicting spellings and types. Temporaries, condition and short-circuit
slots, and closure names now come from one counter that starts above every
identity in the module and is never reset, as the Haskell lowering does.

Reserved LLVM prefix: a function in a namespace named llvm produced a
global whose name began with the prefix LLVM reserves for intrinsics, and
module verification failed. Such symbols are emitted with a leading dollar
sign, which cannot occur in a source identifier; declarations in other
per-source objects follow the same rule.

Each defect has a failing-first component test, and both crashing inputs
join the versioned source corpus. The Core IR and LLVM backend documents
state the allocation and naming rules.
A hosted AddressSanitizer run aborted the differential smoke with
'IMAGE_REL_AMD64_ADDR32NB relocation requires an ordered section layout'
while the same commit passed in a parallel run. On Windows LLJIT links with
RuntimeDyld, whose default memory manager maps code and data sections
separately. Win64 unwind tables use image-relative relocations that can only
be applied when no section of an object lies below the lowest one, so the
operating system's choice of addresses decided whether linking succeeded.

The ORC session now gives each object a single pre-reserved block on COFF
hosts and keeps LLJIT's COFF symbol-flag handling; other hosts keep the
default linking layer. The failure depends on address layout and was not
reproduced locally, so a test samples 400 independent sessions and the
interactive guide states that the evidence for the fix is indirect.
The contiguous-reservation linking layer introduced for COFF hosts used a
SectionMemoryManager constructor that only newer LLVM releases provide.
The block was selected at run time but compiled on every host, so builds
against distribution LLVM releases on macOS, Ubuntu and Fedora failed with
'no matching constructor for initialization of llvm::SectionMemoryManager'.

The linking-layer setup and its two headers are now inside a Windows-only
preprocessor block. Other hosts compile the same session setup as before
that change and keep LLJIT's default linking layer.
@Leitwolf11 Leitwolf11 changed the title Strengthen deep fuzz coverage, differential oracles and campaign evidence Fix native control-flow and symbol miscompiles and complete the fuzz campaign inventory Oct 2, 2026
@Leitwolf11
Leitwolf11 marked this pull request as ready for review October 2, 2026 00:51
@Leitwolf11
Leitwolf11 merged commit ef18d44 into main Oct 2, 2026
73 checks passed
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