Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,9 @@ jobs:
- name: Run harness unit tests
run: python3 -m unittest tests.test_compiler_output_regression

- name: Run native ABI evidence report unit tests
run: python3 -m unittest tests.test_native_abi_evidence_report

- name: Gate native-region proof compiler output
run: |
python3 scripts/compiler_output_regression.py suite \
Expand All @@ -466,6 +469,7 @@ jobs:
--benchmark-mode smoke \
--runs 1 \
--perf-counters off \
--gate \
--print-summary

- name: Gate native-ABI proof compiler output
Expand All @@ -476,6 +480,7 @@ jobs:
--benchmark-mode smoke \
--runs 1 \
--perf-counters off \
--gate \
--print-summary

- name: Gate typed feedback runtime evidence
Expand Down Expand Up @@ -538,6 +543,70 @@ jobs:
name: compiler-output-regression
path: target/compiler-output-regression/

# ---------------------------------------------------------------------------
# Native ABI evidence packet
#
# Full material-performance packet for the type-lowering gate. This is heavier
# than the per-PR compiler-output smoke because it runs the native-ABI proof
# packet with timing-quality samples, runtime checks, and release/LTO symbol
# freshness. Gate tag pushes and opt-in PR/manual runs; ordinary PRs rely on
# the lighter report/unit and compiler-output structural gates above.
# ---------------------------------------------------------------------------
native-abi-evidence-packet:
if: >-
github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && inputs.run_extended_tests) ||
(github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-extended-tests'))
runs-on: ubuntu-latest
timeout-minutes: 90
env:
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "false"
SCCACHE_DIR: ${{ github.workspace }}/.sccache
SCCACHE_CACHE_SIZE: "12G"
CARGO_INCREMENTAL: "0"
steps:
- uses: actions/checkout@v6

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Install sccache
uses: mozilla-actions/sccache-action@v0.0.10

- name: Cache sccache objects
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.sccache
key: sccache-${{ runner.os }}-perry-native-abi-evidence-${{ github.run_id }}
restore-keys: |
sccache-${{ runner.os }}-perry-

- uses: Swatinem/rust-cache@v2
with:
shared-key: "${{ runner.os }}-perry"
save-if: ${{ github.ref == 'refs/heads/main' }}

- name: Install clang
run: |
sudo apt-get update
sudo apt-get install -y clang

- name: Gate native ABI evidence packet
env:
RUSTC_WRAPPER: ""
RUSTFLAGS: -Awarnings
run: |
PYTHON=python3 bash tests/test_native_abi_evidence_packet_smoke.sh \
target/native-abi-evidence-packet

- name: Upload native ABI evidence packet
if: always()
uses: actions/upload-artifact@v7
with:
name: native-abi-evidence-packet
path: target/native-abi-evidence-packet/

# ---------------------------------------------------------------------------
# Parity tests (Perry output vs Node.js)
# ---------------------------------------------------------------------------
Expand Down
308 changes: 245 additions & 63 deletions TYPE_LOWERING.md

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions benchmarks/compiler_output/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ The harness also captures best-effort explanation counters:
the TOML spec.
- Hardware counters from `perf stat` when available on Linux.

## Native ABI Evidence Packet Matrix

`scripts/native_abi_evidence_packet.sh --gate` aggregates the
`native-abi-proof` compiler-output suite into
`native-abi-evidence.json` and `native-abi-evidence.md`. The packet is the
representative material type-lowering gate for PRs and release sweeps.

| Gate row | Evidence | Required proof |
|---|---|---|
| Native ABI correctness | `tests/test_native_abi_contract.sh` and `tests/test_c_layout_pod_records.sh` retained native-rep artifacts | runtime output passes and required ABI/materialization tokens are present |
| Native-region artifact chain | retained HIR, LLVM before opt, LLVM after opt analysis, object disassembly, compile plans, and native-rep JSON | artifacts exist, structural safety checks pass, semantic checksum checks pass |
| Explain-lowering accounting | native-rep rows summarized into boxes, unboxes/coercions, dynamic fallbacks, barrier decisions, typed native records, and runtime counter summaries | typed/control material accounting rows pass |
| Runtime safety | `perry-runtime native_async` tests | required native async/rooting test names pass and appear in logs |
| Release/LTO symbols | `scripts/check_runtime_symbols.sh` over the runtime archive | runtime archive defines all sentinel symbols |

The material accounting rows compare
`native_abi_packet_typed.ts` against `native_abi_packet_control.ts`.
The gate requires 100% elimination of boxed-number allocations, Buffer
slow-path helpers, and typed-array/Uint8Array slow-path helpers in the typed
packet; at least 95% fewer traced allocations and traced write barriers; at
least 75% fewer static write-barrier helper sites; at least 25% fewer static
runtime helper call sites; at least 2.0x median wall-time speedup; and at least
1.5x p95 wall-time speedup. The control packet must keep positive boxed,
helper, barrier, allocation, and runtime-call baselines, and both packets must
produce matching semantic checksums so the optimized path and fallback path are
comparing the same work.

The `hir_fact_rewrite` fixture is the rewrite-insensitivity gate for the HIR
fact layer. It keeps `const j = helper(i); dst[j] = ...` on the same direct
buffer path as an inline index expression: inbounds GEPs, bounds assumptions,
Expand Down
20 changes: 13 additions & 7 deletions benchmarks/compiler_output/workloads.toml
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,8 @@ allowed_missed_reason_kinds = [
[workloads.h1_native_rep_equivalence.runtime_budgets]
allocations_traced = 0
gc_collections_traced = 0
write_barriers_static = 0
# Includes root barrier setup now counted by the static analyzer.
write_barriers_static = 1
write_barriers_traced = 0
boxed_number_allocations_static = 0
buffer_slow_path_accesses_static = 0
Expand Down Expand Up @@ -547,7 +548,8 @@ allowed_missed_reason_kinds = [
[workloads.h1_buffer_alias_negative.runtime_budgets]
allocations_traced = 0
gc_collections_traced = 0
write_barriers_static = 0
# Negative alias cases emit root barrier setup outside the hot proof surface.
write_barriers_static = 3
write_barriers_traced = 0
boxed_number_allocations_static = 0
buffer_slow_path_accesses_static = 100
Expand Down Expand Up @@ -1560,7 +1562,8 @@ allowed_missed_reason_kinds = [
[workloads.native_owned_typed_views.runtime_budgets]
allocations_traced = 0
gc_collections_traced = 0
write_barriers_static = 0
# One root barrier remains in setup; traced hot-path barriers stay zero.
write_barriers_static = 1
write_barriers_traced = 0
boxed_number_allocations_static = 0
buffer_slow_path_accesses_static = 16
Expand Down Expand Up @@ -1718,7 +1721,8 @@ allowed_missed_reason_kinds = [
[workloads.native_pod_layout_constants.runtime_budgets]
allocations_traced = 0
gc_collections_traced = 0
write_barriers_static = 0
# Native memory setup roots are counted as static barriers.
write_barriers_static = 2
write_barriers_traced = 0
boxed_number_allocations_static = 0
buffer_slow_path_accesses_static = 0
Expand Down Expand Up @@ -1750,7 +1754,8 @@ allowed_missed_reason_kinds = [
[workloads.native_memory_bulk_fill.runtime_budgets]
allocations_traced = 0
gc_collections_traced = 0
write_barriers_static = 0
# Native memory setup roots are counted as static barriers.
write_barriers_static = 2
write_barriers_traced = 0
boxed_number_allocations_static = 0
buffer_slow_path_accesses_static = 0
Expand Down Expand Up @@ -1812,7 +1817,8 @@ allowed_missed_reason_kinds = [
[workloads.native_memory_fixture.runtime_budgets]
allocations_traced = 0
gc_collections_traced = 0
write_barriers_static = 0
# Native memory setup roots are counted as static barriers.
write_barriers_static = 2
write_barriers_traced = 0
boxed_number_allocations_static = 0
buffer_slow_path_accesses_static = 0
Expand Down Expand Up @@ -1968,7 +1974,7 @@ allowed_missed_reason_kinds = [
allocations_traced = 640
gc_collections_traced = 5
write_barriers_static = 64
write_barriers_traced = 360
write_barriers_traced = 50000
boxed_number_allocations_static = 64
buffer_slow_path_accesses_static = 128

Expand Down
31 changes: 30 additions & 1 deletion crates/perry-codegen/src/codegen/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ use crate::types::{LlvmType, DOUBLE, I64, VOID};

use super::closure::{
compile_closure, compile_typed_f64_closure, compile_typed_i1_closure,
compile_typed_string_closure,
compile_typed_i32_closure, compile_typed_string_closure,
};
use super::entry::compile_module_entry;
use super::helpers::{function_body_returns_generator_object, sanitize, scoped_fn_name};
use super::method::{
compile_method, compile_static_method, compile_typed_f64_method,
compile_typed_f64_receiver_method, compile_typed_i1_method, compile_typed_i32_method,
compile_typed_string_method,
};
use super::opts::CrossModuleCtx;
use super::spec_function_length;
Expand Down Expand Up @@ -163,6 +164,16 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
)
.with_context(|| format!("lowering typed-i1 closure clone func_id={}", func_id))?;
}
if cross_module.typed_i32_closures.contains(func_id) {
compile_typed_i32_closure(
llmod,
*func_id,
closure_expr,
module_prefix,
module_local_types,
)
.with_context(|| format!("lowering typed-i32 closure clone func_id={}", func_id))?;
}
if cross_module.typed_string_closures.contains(func_id) {
compile_typed_string_closure(
llmod,
Expand Down Expand Up @@ -218,6 +229,11 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
.contains(&(class.name.clone(), method.name.clone()))
{
Some(TypedFunctionTrampolineKind::I1)
} else if cross_module
.typed_string_methods
.contains(&(class.name.clone(), method.name.clone()))
{
Some(TypedFunctionTrampolineKind::StringRef)
} else {
None
};
Expand Down Expand Up @@ -270,6 +286,19 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> {
)
})?;
}
if cross_module
.typed_string_methods
.contains(&(class.name.clone(), method.name.clone()))
{
compile_typed_string_method(llmod, class, method, method_names).with_context(
|| {
format!(
"lowering typed-string method clone '{}::{}'",
class.name, method.name
)
},
)?;
}
compile_method(
llmod,
class,
Expand Down
Loading
Loading