typed/llvmir: compare observed results with the Rust backend (Phase 5)

ober

c500a1d1ff8bbf3aedfd285ffd09be99e01b196e

diff --git a/Makefile b/Makefile
index dbdd37b..94e8797 100644
--- a/Makefile
+++ b/Makefile
@@ -44,12 +44,15 @@ TYPED_LLVMIR_SOURCES ?= tests/fixtures/typed/llvmir-basic.ss tests/fixtures/type
 TYPED_LLVMIR_DIR ?= build/typed/llvmir
 TYPED_LLVMIR_SMOKE_MODULE ?= sample_typed_llvmir_smoke
 TYPED_LLVMIR_SMOKE_EXPECT ?= 42
+TYPED_LLVMIR_PARITY_DIR ?= build/typed/llvmir-parity
+TYPED_LLVMIR_PARITY_SOURCE ?= tests/fixtures/typed/llvmir-smoke.ss
+TYPED_LLVMIR_PARITY_FN ?= sample_typed_llvmir_smoke::main
 # LLVM tool discovery for the typed LLVM IR backend: PATH first, then the
 # Homebrew LLVM keg (Apple Silicon, then Intel). Empty when no LLVM exists.
 LLVM_BIN ?= $(shell if command -v llvm-as >/dev/null 2>&1; then dirname "$$(command -v llvm-as)"; elif [ -x /opt/homebrew/opt/llvm/bin/llvm-as ]; then echo /opt/homebrew/opt/llvm/bin; elif [ -x /usr/local/opt/llvm/bin/llvm-as ]; then echo /usr/local/opt/llvm/bin; fi)
 TYPED_WRAPPER_DIR ?= build/typed/jerboa
 
-.PHONY: help chez chez-cross build binary binary-typed binary-typed-smoke binary-cross native-cross pure-audit typecheck typed-rust typed-llvmir typed-llvmir-check typed-llvmir-smoke typed-wrappers typed-build typed-wrapper-smoke typed-split-tree-smoke typed-test typed-clean test test-reader test-core test-runtime test-stdlib test-ffi test-modules test-expanded test-contract test-ergo test-limits-primitives test-typed-core test-typed-parser test-typed-checker test-typed-rust test-typed-llvmir test-typed-wrappers test-pure-audit test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-security-profile test-native test-gaps native clean-native audit-native clean security security-production security-profile fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict docker-build docker-push
+.PHONY: help chez chez-cross build binary binary-typed binary-typed-smoke binary-cross native-cross pure-audit typecheck typed-rust typed-llvmir typed-llvmir-check typed-llvmir-smoke typed-llvmir-parity typed-wrappers typed-build typed-wrapper-smoke typed-split-tree-smoke typed-test typed-clean test test-reader test-core test-runtime test-stdlib test-ffi test-modules test-expanded test-contract test-ergo test-limits-primitives test-typed-core test-typed-parser test-typed-checker test-typed-rust test-typed-llvmir test-typed-wrappers test-pure-audit test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-security-profile test-native test-gaps native clean-native audit-native clean security security-production security-profile fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict docker-build docker-push
 
 help:
 	@echo "Usage: make <target>"
@@ -86,6 +89,7 @@ help:
 	@echo "  typed-llvmir     Generate textual LLVM IR (.ll) from Typed Jerboa"
 	@echo "  typed-llvmir-check Verify generated .ll with llvm-as + opt"
 	@echo "  typed-llvmir-smoke Build and run an LLVM-backend smoke executable"
+	@echo "  typed-llvmir-parity Compare LLVM and Rust backend observed results"
 	@echo "  typed-wrappers   Generate Jerboa wrappers for typed Rust exports"
 	@echo "  typed-build      Generate Rust crate sources/wrappers and run cargo build"
 	@echo "  typed-wrapper-smoke Build and call generated typed wrappers through FFI"
@@ -838,6 +842,36 @@ typed-llvmir-smoke: typed-llvmir-check
 	    exit 1; \
 	fi
 
+# Behavior parity against the reference Rust backend: lower the same typed
+# source through both backends, run both executables, and require identical
+# observed results (process exit status), not identical generated source.
+# The Rust side builds without cargo: rustc compiles the generated crate's
+# lib.rs as an rlib plus a tiny harness main that calls the same entry.
+typed-llvmir-parity: typed-llvmir-smoke
+	@command -v rustc >/dev/null 2>&1 \
+	    || { echo "typed-llvmir-parity: rustc not found (needed for the Rust reference build)" >&2; exit 1; }
+	@$(SCHEME) --libdirs $(LIBDIRS) --script support/typed-rust.ss \
+	    $(TYPED_LLVMIR_PARITY_DIR)/rust $(TYPED_LLVMIR_PARITY_SOURCE)
+	@rustc --edition 2021 --crate-type rlib \
+	    $(TYPED_LLVMIR_PARITY_DIR)/rust/src/lib.rs \
+	    --crate-name jt_parity -o $(TYPED_LLVMIR_PARITY_DIR)/libjt_parity.rlib \
+	    || { echo "typed-llvmir-parity: rustc rlib build failed" >&2; exit 1; }
+	@printf 'fn main() {\n    std::process::exit(jt_parity::$(TYPED_LLVMIR_PARITY_FN)() as i32);\n}\n' \
+	    > $(TYPED_LLVMIR_PARITY_DIR)/main.rs
+	@rustc --edition 2021 $(TYPED_LLVMIR_PARITY_DIR)/main.rs \
+	    --extern jt_parity=$(TYPED_LLVMIR_PARITY_DIR)/libjt_parity.rlib \
+	    -o $(TYPED_LLVMIR_PARITY_DIR)/parity_rust \
+	    || { echo "typed-llvmir-parity: rustc harness build failed" >&2; exit 1; }
+	@if "$(TYPED_LLVMIR_PARITY_DIR)/parity_rust"; then rust_status=0; else rust_status=$$?; fi; \
+	if "$(TYPED_LLVMIR_DIR)/$(TYPED_LLVMIR_SMOKE_MODULE)"; then llvm_status=0; else llvm_status=$$?; fi; \
+	echo "typed-llvmir-parity: rust=$$rust_status llvm=$$llvm_status expected=$(TYPED_LLVMIR_SMOKE_EXPECT)"; \
+	if [ "$$rust_status" -eq "$$llvm_status" ] && [ "$$rust_status" -eq "$(TYPED_LLVMIR_SMOKE_EXPECT)" ]; then \
+	    echo "typed-llvmir-parity: PASS — backends agree"; \
+	else \
+	    echo "typed-llvmir-parity: FAIL — backend results differ" >&2; \
+	    exit 1; \
+	fi
+
 typed-wrappers:
 	@$(SCHEME) --libdirs $(LIBDIRS) --script support/typed-wrappers.ss $(TYPED_WRAPPER_DIR) $(TYPED_RUST_SOURCES)