[M1-ALLOC-01] Zero sim-loop allocation assertion (G-R1) #113
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================ | |
| # Laige CI — P0 platform matrix on pull request (M0-CI-01) | |
| # | |
| # Roadmap: roadmap/M0-foundations.md, step M0-CI-01 | |
| # PRD refs: §6 (P0 platforms, AC-6.1), §14 (cadence), §8.1 (build budget) | |
| # | |
| # Cadence (PRD §14: "one per PR, all per merge"): each pull request runs | |
| # exactly ONE P0 OS, selected by the PR's ci:* label; the default (no | |
| # label) is Linux. Merges to master run ALL P0 OSes via ci.yml, which is | |
| # the "all per merge" half of the cadence. | |
| # | |
| # Label selector (apply at most one per PR; if several are present the | |
| # highest-priority label wins, so exactly one P0 OS always runs): | |
| # | |
| # ci:macos → macos-arm64 + macos-intel jobs | |
| # ci:windows → windows-msvc job | |
| # ci:linux → linux-gcc + linux-clang jobs (also the no-label default) | |
| # | |
| # NOTE: the labels must be created once in the repository settings | |
| # (GitHub does not create labels from workflows). A PR with an unknown | |
| # or missing ci:* label simply falls back to the Linux default. | |
| # | |
| # Jobs, runner images, steps, and the 10-minute budget timeout are the | |
| # same as in ci.yml (see its header for the full matrix documentation | |
| # and the Windows multi-config note). | |
| # | |
| # Sanitizer lanes (M0-CI-02): linux-asan (LAIGE_ASAN=ON) and linux-tsan | |
| # (LAIGE_TSAN=ON) run under the same condition as the default Linux | |
| # jobs — i.e. on every PR that does not select another P0 OS — because | |
| # they are part of the Linux P0 check (PRD §14: one P0 OS per PR). See | |
| # the ci.yml header for the lane semantics (fatal sanitizer reports, | |
| # report archiving, toolchain choice). | |
| # | |
| # Fuzz lane (PRD §14 "every commit (bounded)"; M0-TEST-01): no separate | |
| # fuzz job — the `fuzz_json_parse` ctest entry (1000 deterministic runs | |
| # of laige-fuzz on the json_parse target) runs inside every build | |
| # job's ctest, instrumented in the ASan tree. Seed and nightly-long-run | |
| # conventions: docs/testing.md. | |
| # | |
| # Include-graph lint (M0-CI-03; NFR-8.11, NFR-8.13): the `include-lint` | |
| # job runs on EVERY pull request, independent of the ci:* label selector | |
| # — it is a platform-independent repository check (Python 3 stdlib only), | |
| # not a P0 OS build, so it does not interact with the "one P0 OS per PR" | |
| # cadence. It enforces the PRD §10.1 include rules over src/** (laige-core | |
| # depends on nothing internal; arrows only downward in the module stack; | |
| # vendored deps only included from their deps.lock `owner`) and reports | |
| # the vendored-dependency count, which must stay ≤ 10 (PRD §11). | |
| # | |
| # Fork PRs: this workflow uses the pull_request event, which runs on the | |
| # merge ref with a read-only token; checkout + build + ctest need nothing | |
| # beyond contents:read. On such PRs the artifact upload (needs | |
| # actions:write) fails with a 403, so the upload steps carry | |
| # continue-on-error: true — the reports then remain in the job log and | |
| # the tee'd output instead of the artifact. | |
| # ============================================================================ | |
| name: CI (pull request) | |
| on: | |
| pull_request: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| linux-gcc: | |
| name: Linux x64 (g++) | |
| if: >- | |
| !contains(github.event.pull_request.labels.*.name, 'ci:macos') && | |
| !contains(github.event.pull_request.labels.*.name, 'ci:windows') | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++ | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Test (unit) | |
| run: ctest --test-dir build --output-on-failure | |
| linux-clang: | |
| name: Linux x64 (clang++) | |
| if: >- | |
| !contains(github.event.pull_request.labels.*.name, 'ci:macos') && | |
| !contains(github.event.pull_request.labels.*.name, 'ci:windows') | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Test (unit) | |
| run: ctest --test-dir build --output-on-failure | |
| linux-asan: | |
| name: Linux x64 ASan+UBSan (clang++) | |
| if: >- | |
| !contains(github.event.pull_request.labels.*.name, 'ci:macos') && | |
| !contains(github.event.pull_request.labels.*.name, 'ci:windows') | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| # Job-scoped token: the archive step below needs actions:write for | |
| # PRs from the base repository (fork PR tokens stay read-only, | |
| # whatever this declares — see header). | |
| permissions: | |
| contents: read | |
| actions: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure (ASan+UBSan) | |
| run: cmake -S . -B build-asan -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DLAIGE_ASAN=ON | |
| - name: Build (ASan+UBSan) | |
| run: cmake --build build-asan -j | |
| - name: Test (unit, ASan+UBSan) | |
| # Every sanitizer report is fatal to the test process: ASan via | |
| # abort_on_error=1:halt_on_error=1 here, UBSan via | |
| # -fno-sanitize-recover=all (wired by CMake). log_path keeps one | |
| # report file per test process for the artifact upload below. | |
| env: | |
| ASAN_OPTIONS: "abort_on_error=1:halt_on_error=1:detect_leaks=1:log_path=${{ github.workspace }}/asan-reports/asan" | |
| run: | | |
| set -o pipefail | |
| ctest --test-dir build-asan --output-on-failure 2>&1 | tee asan-ctest-output.txt | |
| - name: Archive sanitizer reports | |
| # continue-on-error: fork PRs run with a read-only token, so the | |
| # upload 403s there (see header); the reports stay in the job log | |
| # and the tee'd output. The job-level actions:write covers PRs | |
| # from the base repository. | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-asan-reports | |
| path: | | |
| asan-ctest-output.txt | |
| asan-reports/ | |
| build-asan/Testing/Temporary/LastTest.log | |
| linux-tsan: | |
| name: Linux x64 TSan (clang++) | |
| if: >- | |
| !contains(github.event.pull_request.labels.*.name, 'ci:macos') && | |
| !contains(github.event.pull_request.labels.*.name, 'ci:windows') | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| # Job-scoped token: the archive step below needs actions:write (see | |
| # the linux-asan job comment). | |
| permissions: | |
| contents: read | |
| actions: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure (TSan) | |
| run: cmake -S . -B build-tsan -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DLAIGE_TSAN=ON | |
| - name: Build (TSan) | |
| run: cmake --build build-tsan -j | |
| - name: Test (unit, TSan) | |
| # TSAN_OPTIONS=halt_on_error=1 is applied per test by tests/ | |
| # (M0-BUILD-01), so the first data race report kills the test | |
| # process and ctest fails. The race report is printed to stderr | |
| # and captured by the tee below. | |
| run: | | |
| set -o pipefail | |
| ctest --test-dir build-tsan --output-on-failure 2>&1 | tee tsan-ctest-output.txt | |
| - name: Archive sanitizer reports | |
| # continue-on-error and job-level permissions: same rationale as | |
| # the linux-asan job (fork PRs read-only, base-repo PRs can | |
| # upload). | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-tsan-reports | |
| path: | | |
| tsan-ctest-output.txt | |
| build-tsan/Testing/Temporary/LastTest.log | |
| windows-msvc: | |
| name: Windows x64 (MSVC 2022) | |
| if: >- | |
| contains(github.event.pull_request.labels.*.name, 'ci:windows') && | |
| !contains(github.event.pull_request.labels.*.name, 'ci:macos') | |
| runs-on: windows-2022 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| # CMAKE_BUILD_TYPE is ignored by the multi-config VS generator; the | |
| # Debug configuration is pinned on the build/test steps below. | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
| - name: Build | |
| run: cmake --build build --config Debug -j | |
| - name: Test (unit) | |
| # The trait_compile_* fixtures invoke cl.exe DIRECTLY from a | |
| # generated cmake -P script (execute_process), outside the VS | |
| # generator's toolchain setup, so — unlike every other test in | |
| # this job — they need the MSVC environment (INCLUDE/LIB/PATH) | |
| # to find the CRT/STL headers; the plain runner shell lacks it | |
| # and cl fails with C1083 on <cstdint> etc. This step imports | |
| # the VS development environment into the current PowerShell | |
| # process: vswhere locates the installation, VsDevCmd.bat (the | |
| # canonical vcvars, present in every VS 2017+ install under | |
| # Common7\Tools — called by relative name from that directory, | |
| # so no path quoting is involved) sets it, and its `set` dump | |
| # is imported variable by variable. ctest, cmake -P, and cl all | |
| # inherit it. Everything else in this step is unchanged. | |
| run: | | |
| $vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath | |
| Push-Location (Join-Path $vs "Common7\Tools") | |
| $envDump = cmd /c "call VsDevCmd.bat -arch=amd64 -host_arch=amd64 >nul && set" | |
| Pop-Location | |
| foreach ($line in $envDump) { | |
| if ($line -match '^([A-Za-z_][A-Za-z0-9_]*)=(.*)$') { | |
| Set-Item -Path "env:$($matches[1])" -Value $matches[2] | |
| } | |
| } | |
| ctest --test-dir build -C Debug --output-on-failure | |
| macos-arm64: | |
| name: macOS arm64 (AppleClang) | |
| if: contains(github.event.pull_request.labels.*.name, 'ci:macos') | |
| runs-on: macos-15 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Test (unit) | |
| run: ctest --test-dir build --output-on-failure | |
| macos-intel: | |
| name: macOS Intel (AppleClang) | |
| if: contains(github.event.pull_request.labels.*.name, 'ci:macos') | |
| runs-on: macos-14 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Test (unit) | |
| run: ctest --test-dir build --output-on-failure | |
| include-lint: | |
| # M0-CI-03: runs on every PR (no ci:* condition). Platform-independent: | |
| # the lint is Python 3 stdlib only (no setup step needed) and checks | |
| # the repository layout, not a compiler-specific build. The CTest | |
| # suite runs it against the real tree in every P0 job as well | |
| # (tests/tools, test `include-lint-real-tree`), so a broken include | |
| # fails even a PR that selects another P0 OS. | |
| name: Include-graph lint + dependency count | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Lint include graph + report dependency count | |
| run: python3 tools/laige-include-lint | |
| determinism-lint: | |
| # M1-DET-01: runs on every PR (no ci:* condition). The sim-source | |
| # determinism scan (the second half of the G-R8 guarantee; the | |
| # first is the compile-time trait checked by the | |
| # trait_compile_* CTest fixtures): forbids raw float/double and | |
| # unordered containers in src/laige-sim/**, with per-line | |
| # LAIGE-DETERM-EXCEPTION markers as the documented false-positive | |
| # policy (docs/concepts/determinism.md). Platform-independent: | |
| # Python 3 stdlib only, no setup step. The CTest suite runs it | |
| # against fixture trees and the real tree in every P0 job as well | |
| # (tests/tools, tests `determinism-lint-*`). | |
| name: Determinism source scan (sim module) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Scan sim sources for raw FP / unordered containers | |
| run: python3 tools/laige-determinism-lint | |
| api-manifest: | |
| # M0-TOOL-01: runs on every PR (no ci:* condition). The checked-in | |
| # public API manifest (laige-api.json, PRD §9.4, NFR-13.1) must stay | |
| # in sync with the public headers: this job regenerates it from the | |
| # current headers with laige-api-scanner and fails on any drift, so | |
| # adding a public symbol without regenerating the manifest fails CI. | |
| # The CTest suite runs the same check against the real tree in every | |
| # P0 job as well (tests/api, test `api-real-tree`). | |
| name: Public API manifest drift | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
| - name: Build scanner | |
| run: cmake --build build --target laige-api-scanner -j | |
| - name: Check manifest drift | |
| run: ./build/bin/laige-api-scanner --root . --check laige-api.json | |
| detcheck: | |
| # M1-DET-04: runs on every PR (no ci:* condition). The determinism | |
| # checker (FR-11.5): the built-in synthetic scenario self-check, | |
| # then the M1-SAMPLE-01 hello scenario asserted against the | |
| # committed per-tick hash baselines (samples/hello/baselines/) on | |
| # BOTH SimMath backends (fixed_point_16_16, float_pinned_32 — ADR | |
| # 0002) with `hello --expect` (the laige-replay --expect contract: | |
| # 0 match, 1 first divergence, 2 baseline read/contract error). | |
| # The PR's P0 OS job's ctest repeats the same baseline assertion on | |
| # its own platform (tests/sample: hello_baseline_fpx / | |
| # hello_baseline_fp32); this tooling job guarantees the check runs | |
| # on every PR, including ones labelled for non-Linux P0 OSes. The | |
| # full two-configuration matrix (g++ vs clang++, Debug+ASan vs | |
| # Release) runs on merges in the detcheck job of ci.yml. | |
| name: Determinism check | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Synthetic scenario self-check | |
| run: ./build/bin/laige-detcheck --scenario=synthetic | |
| - name: Real scenario vs baselines (M1-SAMPLE-01, both backends) | |
| # The hello template is always built in this job, so both steps | |
| # always execute. Each must exit 0 (a first-divergence report is | |
| # exit 1; a baseline read/contract failure is exit 2) and print | |
| # the 301-line per-tick hash stream. | |
| run: | | |
| ./samples/hello/bin/hello \ | |
| --expect samples/hello/baselines/fixed_point_16_16/hash_stream.txt | |
| ./samples/hello/bin/hello-fp32 \ | |
| --expect samples/hello/baselines/float_pinned_32/hash_stream.txt |