From dc7dcf836a653076eb42887b2b3e8f08e9848040 Mon Sep 17 00:00:00 2001 From: samuelburnham <45365069+samuelburnham@users.noreply.github.com> Date: Fri, 20 Mar 2026 10:50:28 -0400 Subject: [PATCH 1/7] chore: Update lean-ffi --- Cargo.lock | 4 +- Cargo.toml | 3 +- Tests/FFI.lean | 3 +- Tests/FFI/Refcount.lean | 295 +++++++++++++++++++++++++ lakefile.lean | 18 +- src/ffi.rs | 15 +- src/ffi/_iroh.rs | 26 +-- src/ffi/aiur.rs | 6 +- src/ffi/aiur/protocol.rs | 143 ++++++------ src/ffi/aiur/toplevel.rs | 88 ++++---- src/ffi/builder.rs | 8 +- src/ffi/byte_array.rs | 7 +- src/ffi/compile.rs | 444 +++++++++++++++++++++----------------- src/ffi/graph.rs | 38 ++-- src/ffi/iroh.rs | 35 +-- src/ffi/ix.rs | 8 - src/ffi/ix/address.rs | 21 +- src/ffi/ix/constant.rs | 171 ++++++++------- src/ffi/ix/data.rs | 182 +++++++++------- src/ffi/ix/env.rs | 80 ++++--- src/ffi/ix/expr.rs | 244 +++++++++++---------- src/ffi/ix/level.rs | 54 ++--- src/ffi/ix/name.rs | 45 ++-- src/ffi/ixon.rs | 13 +- src/ffi/ixon/compare.rs | 50 +++-- src/ffi/ixon/constant.rs | 420 +++++++++++++++++++++--------------- src/ffi/ixon/enums.rs | 49 +++-- src/ffi/ixon/env.rs | 191 ++++++++-------- src/ffi/ixon/expr.rs | 136 ++++++------ src/ffi/ixon/meta.rs | 306 +++++++++++++++----------- src/ffi/ixon/serialize.rs | 30 +-- src/ffi/ixon/sharing.rs | 32 +-- src/ffi/ixon/univ.rs | 50 +++-- src/ffi/keccak.rs | 16 +- src/ffi/lean_env.rs | 238 ++++++++++---------- src/ffi/primitives.rs | 77 ++++--- src/ffi/refcount.rs | 431 ++++++++++++++++++++++++++++++++++++ src/ffi/unsigned.rs | 10 +- src/lean.rs | 53 +++-- src/sha256.rs | 6 +- 40 files changed, 2576 insertions(+), 1470 deletions(-) create mode 100644 Tests/FFI/Refcount.lean create mode 100644 src/ffi/refcount.rs diff --git a/Cargo.lock b/Cargo.lock index bcc096b4..c3ade3fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1005,7 +1005,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.2.1", + "windows-link 0.1.3", "windows-result 0.4.1", ] @@ -1846,7 +1846,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lean-ffi" version = "0.1.0" -source = "git+https://github.com/argumentcomputer/lean-ffi?rev=91185181da859eb3941df2fbede24ae03838ed5b#91185181da859eb3941df2fbede24ae03838ed5b" +source = "git+https://github.com/argumentcomputer/lean-ffi?rev=53c90ecf051023158b727e926a6141e72b106395#53c90ecf051023158b727e926a6141e72b106395" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index 567f443c..55a915a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ anyhow = "1" blake3 = "1.8.2" itertools = "0.14.0" indexmap = { version = "2", features = ["rayon"] } -lean-ffi = { git = "https://github.com/argumentcomputer/lean-ffi", rev = "91185181da859eb3941df2fbede24ae03838ed5b" } +lean-ffi = { git = "https://github.com/argumentcomputer/lean-ffi", rev = "53c90ecf051023158b727e926a6141e72b106395" } multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "bdb0d7d66c02b554e66c449da2dbf12dc0dc27af" } num-bigint = "0.4.6" rayon = "1" @@ -40,6 +40,7 @@ quickcheck_macros = "1.0.0" [features] default = [] parallel = ["multi-stark/parallel"] +test-ffi = [] net = ["bytes", "tokio", "iroh", "iroh-base", "n0-snafu", "n0-watcher", "rand", "tracing", "tracing-subscriber", "bincode", "serde" ] [profile.dev] diff --git a/Tests/FFI.lean b/Tests/FFI.lean index dedad854..35573013 100644 --- a/Tests/FFI.lean +++ b/Tests/FFI.lean @@ -8,10 +8,11 @@ public import Tests.FFI.Basic public import Tests.FFI.Ix public import Tests.FFI.Ixon public import Tests.FFI.Lifecycle +public import Tests.FFI.Refcount namespace Tests.FFI public def suite : List LSpec.TestSeq := - Tests.FFI.Basic.suite ++ Tests.FFI.Ix.suite ++ Tests.FFI.Ixon.suite ++ Tests.FFI.Lifecycle.suite + Tests.FFI.Basic.suite ++ Tests.FFI.Ix.suite ++ Tests.FFI.Ixon.suite ++ Tests.FFI.Lifecycle.suite ++ Tests.FFI.Refcount.suite end Tests.FFI diff --git a/Tests/FFI/Refcount.lean b/Tests/FFI/Refcount.lean new file mode 100644 index 00000000..7c012042 --- /dev/null +++ b/Tests/FFI/Refcount.lean @@ -0,0 +1,295 @@ +/- + Reference counting and ownership tests for the typed lean-ffi API. + Requires `IX_TEST_FFI=1` to enable the `test-ffi` Cargo feature. + + These tests exercise ownership semantics that catch double-free, + use-after-free, and refcount leaks by calling Rust FFI functions that: + + - Convert borrowed → owned via to_owned_ref (lean_inc) + - Clone owned values then drop both (two lean_dec calls) + - Take owned params and drop them (lean_dec on complex structures) + - Traverse deeply nested borrows without inc_ref + - Build from cache (clone-based dedup) then drop the array + - Repeatedly alloc and immediately drop (stress alloc/dealloc) + - Handle persistent objects (m_rc == 0, inc/dec are no-ops) +-/ +module + +public import LSpec +public import Tests.Gen.Ix +public import Ix.Environment +public import Ix.Address + +open LSpec SlimCheck Gen +open Tests.Gen.Ix + +namespace Tests.FFI.Refcount + +/-! ## FFI declarations — gated behind test-ffi Cargo feature -/ + +-- Borrow-to-owned: take @&, return owned copy via to_owned_ref +@[extern "rs_refcount_borrow_to_owned_name"] +opaque borrowToOwnedName : @& Ix.Name → Ix.Name + +@[extern "rs_refcount_borrow_to_owned_level"] +opaque borrowToOwnedLevel : @& Ix.Level → Ix.Level + +@[extern "rs_refcount_borrow_to_owned_expr"] +opaque borrowToOwnedExpr : @& Ix.Expr → Ix.Expr + +-- Multiple borrows from same parent +@[extern "rs_refcount_multi_borrow_name"] +opaque multiBorrowName : @& Ix.Name → String + +-- Deep borrow traversal +@[extern "rs_refcount_deep_borrow_expr"] +opaque deepBorrowExpr : @& Ix.Expr → Nat + +-- Owned drop (NOT @&, Rust must lean_dec) +@[extern "rs_refcount_owned_array_drop"] +opaque ownedArrayDrop : Array Ix.Name → Nat + +@[extern "rs_refcount_owned_list_drop"] +opaque ownedListDrop : List Ix.Expr → Nat + +-- Clone and compare +@[extern "rs_refcount_clone_and_compare"] +opaque cloneAndCompare : @& Ix.Name → Nat + +-- Roundtrip loop (N decode/rebuild cycles) +@[extern "rs_refcount_roundtrip_loop"] +opaque roundtripLoop : @& Ix.Name → USize → Ix.Name + +-- Nested borrowed collection traversal +@[extern "rs_refcount_nested_borrow"] +opaque nestedBorrow : @& Array (Ix.Name × Ix.Level) → Nat + +-- Cache dedup: build N copies from cache +@[extern "rs_refcount_cache_dedup"] +opaque cacheDedup : @& Ix.Name → USize → Array Ix.Name + +-- Persistent object checks +@[extern "rs_refcount_is_persistent"] +opaque isPersistent : @& Ix.Name → UInt8 + +@[extern "rs_refcount_persistent_roundtrip"] +opaque persistentRoundtrip : @& Ix.Name → Ix.Name + +-- String lifecycle +@[extern "rs_refcount_string_lifecycle"] +opaque stringLifecycle : @& String → String + +-- Alloc/drop stress loop +@[extern "rs_refcount_alloc_drop_loop"] +opaque allocDropLoop : @& Ix.Name → USize → Ix.Name + +-- Array element borrow roundtrip +@[extern "rs_refcount_array_element_borrow"] +opaque arrayElementBorrow : @& Array Ix.Level → Array Ix.Level + +-- Multi-threaded tests using LeanShared on ix domain types +@[extern "rs_mt_parallel_decode_names"] +opaque mtParallelDecodeNames : @& Array Ix.Name → USize → Nat + +@[extern "rs_mt_parallel_decode_exprs"] +opaque mtParallelDecodeExprs : @& Array Ix.Expr → USize → Nat + +@[extern "rs_mt_parallel_roundtrip_names"] +opaque mtParallelRoundtripNames : @& Array Ix.Name → USize → Array Ix.Name + +@[extern "rs_mt_shared_expr_stress"] +opaque mtSharedExprStress : @& Ix.Expr → USize → USize → Nat + +/-! ## Test data -/ + +private def testAnon : Ix.Name := Ix.Name.mkAnon +private def testStr : Ix.Name := Ix.Name.mkStr (Ix.Name.mkStr Ix.Name.mkAnon "foo") "bar" +private def testNum : Ix.Name := Ix.Name.mkNat (Ix.Name.mkStr Ix.Name.mkAnon "x") 42 +private def testLevel0 : Ix.Level := Ix.Level.mkZero +private def testLevel1 : Ix.Level := Ix.Level.mkSucc Ix.Level.mkZero +private def testLevelMax : Ix.Level := Ix.Level.mkMax Ix.Level.mkZero (Ix.Level.mkSucc Ix.Level.mkZero) + +-- Module-level definitions become persistent (m_rc == 0) after initialization +private def persistentName : Ix.Name := Ix.Name.mkStr Ix.Name.mkAnon "persistent" + +/-! ## Borrow-to-owned tests + Verifies that to_owned_ref (lean_inc) produces a valid owned handle. + A refcount bug here would manifest as use-after-free or double-free. -/ + +def borrowToOwnedTests : TestSeq := + test "borrow→owned Name anonymous" (borrowToOwnedName testAnon == testAnon) ++ + test "borrow→owned Name str" (borrowToOwnedName testStr == testStr) ++ + test "borrow→owned Name num" (borrowToOwnedName testNum == testNum) ++ + test "borrow→owned Level zero" (borrowToOwnedLevel testLevel0 == testLevel0) ++ + test "borrow→owned Level succ" (borrowToOwnedLevel testLevel1 == testLevel1) ++ + test "borrow→owned Level max" (borrowToOwnedLevel testLevelMax == testLevelMax) + +/-! ## Multiple borrows from same object + Verifies that reading fields multiple times from the same borrowed ctor + doesn't corrupt data (no dangling pointer from intermediate borrow). -/ + +def multiBorrowTests : TestSeq := + let name := Ix.Name.mkStr Ix.Name.mkAnon "hello" + test "multi-borrow Name str" (multiBorrowName name == "hello|hello") + +/-! ## Deep borrow tests + Recursively traverses an Expr tree using only borrowed refs. + Would crash if any intermediate borrow was invalid. -/ + +def deepBorrowTests : TestSeq := + let e0 := Ix.Expr.mkBVar 0 + let e1 := Ix.Expr.mkBVar 1 + let e2 := Ix.Expr.mkBVar 2 + let e3 := Ix.Expr.mkBVar 3 + let app1 := Ix.Expr.mkApp e0 e1 + let app2 := Ix.Expr.mkApp app1 e2 + let app3 := Ix.Expr.mkApp app2 e3 + -- 3 app nodes + 4 bvar leaves = 7 total + test "deep borrow expr 7 nodes" (deepBorrowExpr app3 == 7) + +/-! ## Owned drop tests + Passes owned (NOT @&) arrays/lists. Rust must lean_dec each element + on drop. A missing lean_dec leaks; an extra lean_dec crashes. -/ + +def ownedDropTests : TestSeq := + let names := #[testAnon, testStr, testNum] + test "owned array drop 3 names" (ownedArrayDrop names == 3) ++ + test "owned array drop empty" (ownedArrayDrop #[] == 0) ++ + let exprs := [Ix.Expr.mkBVar 0, Ix.Expr.mkBVar 1, Ix.Expr.mkSort Ix.Level.mkZero] + test "owned list drop 3 exprs" (ownedListDrop exprs == 3) ++ + test "owned list drop empty" (ownedListDrop [] == 0) + +/-! ## Clone tests + Makes two owned clones of a borrowed Name, decodes both. + After decode, both clones are dropped → two lean_dec_ref calls. + Would double-free if clone didn't lean_inc. -/ + +def cloneTests : TestSeq := + test "clone anonymous" (cloneAndCompare testAnon == 1) ++ + test "clone str name" (cloneAndCompare testStr == 1) ++ + test "clone num name" (cloneAndCompare testNum == 1) + +/-! ## Roundtrip loop tests + Each iteration: decode borrowed → build new owned → drop old → repeat. + Stresses N alloc/dealloc cycles. Would crash on any refcount error. -/ + +def roundtripLoopTests : TestSeq := + test "roundtrip loop 1x" (roundtripLoop testStr 1 == testStr) ++ + test "roundtrip loop 5x" (roundtripLoop testStr 5 == testStr) ++ + test "roundtrip loop 20x" (roundtripLoop testStr 20 == testStr) ++ + test "roundtrip loop anon 10x" (roundtripLoop testAnon 10 == testAnon) ++ + test "roundtrip loop num 10x" (roundtripLoop testNum 10 == testNum) + +/-! ## Nested borrow tests + Iterates a borrowed Array of pairs, accessing both fields of each pair. + Would crash if array element borrows outlived the parent. -/ + +def nestedBorrowTests : TestSeq := + let pairs : Array (Ix.Name × Ix.Level) := #[ + (testAnon, testLevel0), + (testStr, testLevel1), + (testNum, testLevelMax) + ] + test "nested borrow 3 pairs" (nestedBorrow pairs == 3) ++ + test "nested borrow empty" (nestedBorrow #[] == 0) + +/-! ## Persistent object tests + Module-level defs have m_rc == 0. lean_inc/lean_dec are no-ops. + Verifies that the typed API handles these correctly. -/ + +def persistentTests : TestSeq := + test "persistent name is_persistent" (isPersistent persistentName == 1) ++ + test "persistent roundtrip" (persistentRoundtrip persistentName == persistentName) + +/-! ## Cache dedup tests + Builds N copies of the same Name via LeanBuildCache, which clones + (lean_inc) cached entries. Dropping the array lean_decs each copy. + Would crash if clone refcount was wrong. -/ + +def cacheDedupTests : TestSeq := + let duped := cacheDedup testStr 5 + test "cache dedup size 5" (duped.size == 5) ++ + test "cache dedup all equal" (duped.all (· == testStr)) ++ + let duped20 := cacheDedup testNum 20 + test "cache dedup size 20" (duped20.size == 20 && duped20.all (· == testNum)) + +/-! ## String lifecycle tests -/ + +def stringTests : TestSeq := + test "string lifecycle" (stringLifecycle "hello" == "olleh") ++ + test "string lifecycle empty" (stringLifecycle "" == "") ++ + test "string lifecycle unicode" (stringLifecycle "café" == "éfac") + +/-! ## Alloc/drop stress tests + Builds and immediately drops N Lean objects in a loop. -/ + +def allocDropTests : TestSeq := + test "alloc/drop 100x" (allocDropLoop testStr 100 == testStr) ++ + test "alloc/drop 1000x" (allocDropLoop testNum 1000 == testNum) + +/-! ## Array element borrow tests + Borrows each element of a borrowed array, decodes, rebuilds. -/ + +def arrayElementTests : TestSeq := + let levels := #[testLevel0, testLevel1, testLevelMax] + let result := arrayElementBorrow levels + test "array element borrow size" (result.size == 3) ++ + test "array element borrow eq" (result == levels) + +/-! ## Multi-threaded LeanShared tests + These exercise lean_mark_mt + atomic refcounting on ix domain types + across real OS threads. A refcount bug would cause SIGSEGV or assertion failure. -/ + +def mtTests : TestSeq := + let names := #[testAnon, testStr, testNum] + -- Parallel decode: N threads × 3 names each + test "MT parallel decode names 4 threads" (mtParallelDecodeNames names 4 == 12) ++ + test "MT parallel decode names 1 thread" (mtParallelDecodeNames names 1 == 3) ++ + test "MT parallel decode names empty" (mtParallelDecodeNames #[] 4 == 0) ++ + -- Parallel decode exprs + let exprs := #[ + Ix.Expr.mkBVar 0, + Ix.Expr.mkApp (Ix.Expr.mkBVar 0) (Ix.Expr.mkBVar 1), + Ix.Expr.mkSort Ix.Level.mkZero + ] + -- bvar=1, app(bvar,bvar)=3, sort=1 → 5 nodes per thread + test "MT parallel decode exprs 4 threads" (mtParallelDecodeExprs exprs 4 == 20) ++ + test "MT parallel decode exprs 1 thread" (mtParallelDecodeExprs exprs 1 == 5) ++ + -- Parallel roundtrip: decode and rebuild from multiple threads + test "MT parallel roundtrip names" (mtParallelRoundtripNames names 4 == names) ++ + test "MT parallel roundtrip empty" (mtParallelRoundtripNames #[] 4 == #[]) ++ + -- Stress test: rapid clone/drop on complex expr + let complexExpr := Ix.Expr.mkApp + (Ix.Expr.mkApp (Ix.Expr.mkBVar 0) (Ix.Expr.mkBVar 1)) + (Ix.Expr.mkSort Ix.Level.mkZero) + test "MT shared expr stress 4×100" (mtSharedExprStress complexExpr 4 100 == 4) ++ + test "MT shared expr stress 8×50" (mtSharedExprStress complexExpr 8 50 == 8) + +/-! ## Property tests -/ + +def propertyTests : TestSeq := + checkIO "borrow→owned Name" (∀ n : Ix.Name, borrowToOwnedName n == n) ++ + checkIO "borrow→owned Level" (∀ l : Ix.Level, borrowToOwnedLevel l == l) ++ + checkIO "clone always equal" (∀ n : Ix.Name, cloneAndCompare n == 1) ++ + checkIO "roundtrip loop 3x" (∀ n : Ix.Name, roundtripLoop n 3 == n) + +/-! ## Test Suite -/ + +public def suite : List TestSeq := [ + borrowToOwnedTests, + multiBorrowTests, + deepBorrowTests, + ownedDropTests, + cloneTests, + roundtripLoopTests, + nestedBorrowTests, + persistentTests, + cacheDedupTests, + stringTests, + allocDropTests, + arrayElementTests, + mtTests, + propertyTests, +] + +end Tests.FFI.Refcount diff --git a/lakefile.lean b/lakefile.lean index 6befd6d9..7cbac727 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -72,15 +72,21 @@ section FFI /-- Build the static lib for the Rust crate -/ extern_lib ix_rs pkg := do - -- Defaults to `--features parallel`, configured via env var + -- Feature flags, configured via env vars: + -- IX_NO_PAR=1 — disable parallel feature + -- IX_NET=1 — enable networking (iroh) + -- IX_RELEASE=1 — strip test-ffi code for release builds + -- Cargo output is visible with `lake -v build`. let ixNoPar ← IO.getEnv "IX_NO_PAR" let ixNet ← IO.getEnv "IX_NET" + let ixRelease ← IO.getEnv "IX_RELEASE" let buildArgs := #["build", "--release"] - let args := match (ixNoPar, ixNet) with - | (some "1", some "1") => buildArgs ++ ["--features", "net"] - | (some "1", _) => buildArgs - | (_, some "1") => buildArgs ++ ["--features", "parallel,net"] - | _ => buildArgs ++ ["--features", "parallel"] + let mut features : Array String := #[] + if ixNoPar != some "1" then features := features.push "parallel" + if ixNet == some "1" then features := features.push "net" + if ixRelease != some "1" then features := features.push "test-ffi" + let args := if features.isEmpty then buildArgs + else buildArgs ++ ["--features", ",".intercalate features.toList] proc { cmd := "cargo", args, cwd := pkg.dir } (quiet := true) let libName := nameToStaticLib "ix_rs" inputBinFile $ pkg.dir / "target" / "release" / libName diff --git a/src/ffi.rs b/src/ffi.rs index 8af44040..64558b13 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -23,15 +23,19 @@ pub mod graph; // Graph/SCC: rs_build_ref_graph, rs_compute_sccs pub mod ix; // Ix types: Name, Level, Expr, ConstantInfo, Environment pub mod ixon; // Ixon types: Univ, Expr, Constant, metadata pub mod primitives; // Primitives: rs_roundtrip_nat, rs_roundtrip_string, etc. +#[cfg(feature = "test-ffi")] +pub mod refcount; // Reference counting / ownership tests (test-only) -use lean_ffi::object::{LeanArray, LeanByteArray, LeanIOResult}; +use lean_ffi::object::{LeanIOResult, LeanOwned}; +#[cfg(feature = "test-ffi")] +use lean_ffi::object::{LeanArray, LeanBorrowed, LeanByteArray, LeanRef}; /// Guard an FFI function that returns a Lean IO result against panics. /// On panic, returns a Lean IO error with the panic message instead of /// unwinding across the `extern "C"` boundary (which is undefined behavior). -pub(crate) fn ffi_io_guard(f: F) -> LeanIOResult +pub(crate) fn ffi_io_guard(f: F) -> LeanIOResult where - F: FnOnce() -> LeanIOResult + std::panic::UnwindSafe, + F: FnOnce() -> LeanIOResult + std::panic::UnwindSafe, { match std::panic::catch_unwind(f) { Ok(result) => result, @@ -48,10 +52,11 @@ where } } +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_boxed_u32s_are_equivalent_to_bytes( - u32s: LeanArray, - bytes: LeanByteArray, + u32s: LeanArray, + bytes: LeanByteArray>, ) -> bool { let u32s_flat: Vec = u32s .map(|elem| elem.unbox_u32()) diff --git a/src/ffi/_iroh.rs b/src/ffi/_iroh.rs index be0d5c4d..30686330 100644 --- a/src/ffi/_iroh.rs +++ b/src/ffi/_iroh.rs @@ -1,4 +1,6 @@ -use lean_ffi::object::{LeanArray, LeanExcept, LeanString}; +use lean_ffi::object::{ + LeanArray, LeanBorrowed, LeanExcept, LeanOwned, LeanString, +}; const ERR_MSG: &str = "Iroh functions not supported when the Rust `net` feature is disabled \ or on MacOS aarch64-darwin"; @@ -6,28 +8,28 @@ const ERR_MSG: &str = "Iroh functions not supported when the Rust `net` feature /// `Iroh.Connect.putBytes' : @& String → @& Array String → @& String → @& String → Except String PutResponse` #[unsafe(no_mangle)] extern "C" fn rs_iroh_put( - _node_id: LeanString, - _addrs: LeanArray, - _relay_url: LeanString, - _input: LeanString, -) -> LeanExcept { + _node_id: LeanString>, + _addrs: LeanArray>, + _relay_url: LeanString>, + _input: LeanString>, +) -> LeanExcept { LeanExcept::error_string(ERR_MSG) } /// `Iroh.Connect.getBytes' : @& String → @& Array String → @& String → @& String → Except String GetResponse` #[unsafe(no_mangle)] extern "C" fn rs_iroh_get( - _node_id: LeanString, - _addrs: LeanArray, - _relay_url: LeanString, - _hash: LeanString, -) -> LeanExcept { + _node_id: LeanString>, + _addrs: LeanArray>, + _relay_url: LeanString>, + _hash: LeanString>, +) -> LeanExcept { LeanExcept::error_string(ERR_MSG) } /// `Iroh.Serve.serve' : Unit → Except String Unit` #[unsafe(no_mangle)] -extern "C" fn rs_iroh_serve() -> LeanExcept { +extern "C" fn rs_iroh_serve() -> LeanExcept { LeanExcept::error_string( "Iroh functions not supported when the Rust `net` feature is disabled \ or on MacOS aarch64-darwin", diff --git a/src/ffi/aiur.rs b/src/ffi/aiur.rs index ed31c634..aa565fd2 100644 --- a/src/ffi/aiur.rs +++ b/src/ffi/aiur.rs @@ -4,16 +4,16 @@ pub mod protocol; pub mod toplevel; use crate::aiur::G; -use lean_ffi::object::LeanObject; +use lean_ffi::object::LeanRef; #[inline] -pub(super) fn lean_unbox_nat_as_usize(obj: LeanObject) -> usize { +pub(super) fn lean_unbox_nat_as_usize(obj: &impl LeanRef) -> usize { assert!(obj.is_scalar()); obj.unbox_usize() } #[inline] -pub(super) fn lean_unbox_g(obj: LeanObject) -> G { +pub(super) fn lean_unbox_g(obj: &impl LeanRef) -> G { let u64 = obj.unbox_u64(); unsafe { G::from_canonical_unchecked(u64) } } diff --git a/src/ffi/aiur/protocol.rs b/src/ffi/aiur/protocol.rs index fb56a543..19d9795d 100644 --- a/src/ffi/aiur/protocol.rs +++ b/src/ffi/aiur/protocol.rs @@ -7,8 +7,8 @@ use rustc_hash::{FxBuildHasher, FxHashMap}; use std::sync::OnceLock; use lean_ffi::object::{ - ExternalClass, LeanArray, LeanByteArray, LeanCtor, LeanExcept, LeanExternal, - LeanNat, LeanObject, + ExternalClass, LeanArray, LeanBorrowed, LeanByteArray, LeanCtor, LeanExcept, + LeanExternal, LeanNat, LeanOwned, LeanRef, }; use crate::{ @@ -47,8 +47,8 @@ fn system_class() -> &'static ExternalClass { /// `Aiur.Proof.toBytes : @& Proof → ByteArray` #[unsafe(no_mangle)] extern "C" fn rs_aiur_proof_to_bytes( - proof_obj: LeanExternal, -) -> LeanByteArray { + proof_obj: LeanExternal>, +) -> LeanByteArray { let bytes = proof_obj.get().to_bytes().expect("Serialization error"); LeanByteArray::from_bytes(&bytes) } @@ -56,8 +56,8 @@ extern "C" fn rs_aiur_proof_to_bytes( /// `Aiur.Proof.ofBytes : @& ByteArray → Proof` #[unsafe(no_mangle)] extern "C" fn rs_aiur_proof_of_bytes( - byte_array: LeanByteArray, -) -> LeanExternal { + byte_array: LeanByteArray>, +) -> LeanExternal { let proof = Proof::from_bytes(byte_array.as_bytes()).expect("Deserialization error"); LeanExternal::alloc(proof_class(), proof) @@ -66,12 +66,12 @@ extern "C" fn rs_aiur_proof_of_bytes( /// `AiurSystem.build : @&Bytecode.Toplevel → @&CommitmentParameters → AiurSystem` #[unsafe(no_mangle)] extern "C" fn rs_aiur_system_build( - toplevel: LeanAiurToplevel, - commitment_parameters: LeanAiurCommitmentParameters, -) -> LeanExternal { + toplevel: LeanAiurToplevel>, + commitment_parameters: LeanAiurCommitmentParameters>, +) -> LeanExternal { let system = AiurSystem::build( - decode_toplevel(toplevel), - decode_commitment_parameters(commitment_parameters), + decode_toplevel(&toplevel), + decode_commitment_parameters(&commitment_parameters), ); LeanExternal::alloc(system_class(), system) } @@ -79,15 +79,15 @@ extern "C" fn rs_aiur_system_build( /// `AiurSystem.verify : @& AiurSystem → @& FriParameters → @& Array G → @& Proof → Except String Unit` #[unsafe(no_mangle)] extern "C" fn rs_aiur_system_verify( - aiur_system_obj: LeanExternal, - fri_parameters: LeanAiurFriParameters, - claim: LeanArray, - proof_obj: LeanExternal, -) -> LeanExcept { - let fri_parameters = decode_fri_parameters(fri_parameters); - let claim = claim.map(lean_unbox_g); + aiur_system_obj: LeanExternal>, + fri_parameters: LeanAiurFriParameters>, + claim: LeanArray>, + proof_obj: LeanExternal>, +) -> LeanExcept { + let fri_parameters = decode_fri_parameters(&fri_parameters); + let claim = claim.map(|x| lean_unbox_g(&x)); match aiur_system_obj.get().verify(fri_parameters, &claim, proof_obj.get()) { - Ok(()) => LeanExcept::ok(LeanObject::box_usize(0)), + Ok(()) => LeanExcept::ok(LeanOwned::box_usize(0)), Err(err) => LeanExcept::error_string(&format!("{err:?}")), } } @@ -96,46 +96,46 @@ extern "C" fn rs_aiur_system_verify( /// `Array G × Array G × Array (Array G × IOKeyInfo)` #[unsafe(no_mangle)] extern "C" fn rs_aiur_toplevel_execute( - toplevel: LeanAiurToplevel, - fun_idx: LeanNat, - args: LeanArray, - io_data_arr: LeanArray, - io_map_arr: LeanArray, -) -> LeanObject { - let toplevel = decode_toplevel(toplevel); - let fun_idx = lean_unbox_nat_as_usize(*fun_idx); - let mut io_buffer = decode_io_buffer(io_data_arr, io_map_arr); + toplevel: LeanAiurToplevel>, + fun_idx: LeanNat>, + args: LeanArray>, + io_data_arr: LeanArray>, + io_map_arr: LeanArray>, +) -> LeanOwned { + let toplevel = decode_toplevel(&toplevel); + let fun_idx = lean_unbox_nat_as_usize(fun_idx.inner()); + let mut io_buffer = decode_io_buffer(&io_data_arr, &io_map_arr); let (_query_record, output) = - toplevel.execute(fun_idx, args.map(lean_unbox_g), &mut io_buffer); + toplevel.execute(fun_idx, args.map(|x| lean_unbox_g(&x)), &mut io_buffer); let lean_io = build_lean_io_buffer(&io_buffer); let result = LeanCtor::alloc(0, 2, 0); result.set(0, build_g_array(&output)); result.set(1, lean_io); - *result + result.into() } /// `AiurSystem.prove`: runs the prover and returns /// `Array G × Proof × Array G × Array (Array G × IOKeyInfo)` #[unsafe(no_mangle)] extern "C" fn rs_aiur_system_prove( - aiur_system_obj: LeanExternal, - fri_parameters: LeanAiurFriParameters, - fun_idx: LeanNat, - args: LeanArray, - io_data_arr: LeanArray, - io_map_arr: LeanArray, -) -> LeanObject { - let fri_parameters = decode_fri_parameters(fri_parameters); - let fun_idx = lean_unbox_nat_as_usize(*fun_idx); - let args = args.map(lean_unbox_g); - let mut io_buffer = decode_io_buffer(io_data_arr, io_map_arr); + aiur_system_obj: LeanExternal>, + fri_parameters: LeanAiurFriParameters>, + fun_idx: LeanNat>, + args: LeanArray>, + io_data_arr: LeanArray>, + io_map_arr: LeanArray>, +) -> LeanOwned { + let fri_parameters = decode_fri_parameters(&fri_parameters); + let fun_idx = lean_unbox_nat_as_usize(fun_idx.inner()); + let args = args.map(|x| lean_unbox_g(&x)); + let mut io_buffer = decode_io_buffer(&io_data_arr, &io_map_arr); let (claim, proof) = aiur_system_obj.get().prove(fri_parameters, fun_idx, &args, &mut io_buffer); - let lean_proof = *LeanExternal::alloc(proof_class(), proof); + let lean_proof: LeanOwned = LeanExternal::alloc(proof_class(), proof).into(); let lean_io = build_lean_io_buffer(&io_buffer); // Proof × Array G × Array (Array G × IOKeyInfo) let proof_io_tuple = LeanCtor::alloc(0, 2, 0); @@ -144,8 +144,8 @@ extern "C" fn rs_aiur_system_prove( // Array G × Proof × Array G × Array (Array G × IOKeyInfo) let result = LeanCtor::alloc(0, 2, 0); result.set(0, build_g_array(&claim)); - result.set(1, *proof_io_tuple); - *result + result.set(1, proof_io_tuple); + result.into() } // ============================================================================= @@ -153,73 +153,80 @@ extern "C" fn rs_aiur_system_prove( // ============================================================================= /// Build a Lean `Array G` from a slice of field elements. -fn build_g_array(values: &[G]) -> LeanArray { +fn build_g_array(values: &[G]) -> LeanArray { let arr = LeanArray::alloc(values.len()); for (i, g) in values.iter().enumerate() { - arr.set(i, LeanObject::box_u64(g.as_canonical_u64())); + arr.set(i, LeanOwned::box_u64(g.as_canonical_u64())); } arr } -fn decode_io_buffer(io_data_arr: LeanArray, io_map_arr: LeanArray) -> IOBuffer { - let data = io_data_arr.map(lean_unbox_g); +fn decode_io_buffer( + io_data_arr: &LeanArray>, + io_map_arr: &LeanArray>, +) -> IOBuffer { + let data = io_data_arr.map(|x| lean_unbox_g(&x)); let map = decode_io_buffer_map(io_map_arr); IOBuffer { data, map } } /// Build a Lean `Array G × Array (Array G × IOKeyInfo)` from an `IOBuffer`. -fn build_lean_io_buffer(io_buffer: &IOBuffer) -> LeanObject { +fn build_lean_io_buffer(io_buffer: &IOBuffer) -> LeanOwned { let lean_io_data = build_g_array(&io_buffer.data); let lean_io_map = { let arr = LeanArray::alloc(io_buffer.map.len()); for (i, (key, info)) in io_buffer.map.iter().enumerate() { let key_arr = build_g_array(key); let key_info = LeanCtor::alloc(0, 2, 0); - key_info.set(0, LeanObject::box_usize(info.idx)); - key_info.set(1, LeanObject::box_usize(info.len)); + key_info.set(0, LeanOwned::box_usize(info.idx)); + key_info.set(1, LeanOwned::box_usize(info.len)); let map_elt = LeanCtor::alloc(0, 2, 0); map_elt.set(0, key_arr); - map_elt.set(1, *key_info); - arr.set(i, *map_elt); + map_elt.set(1, key_info); + arr.set(i, map_elt); } - *arr + arr }; let io_tuple = LeanCtor::alloc(0, 2, 0); io_tuple.set(0, lean_io_data); io_tuple.set(1, lean_io_map); - *io_tuple + io_tuple.into() } fn decode_commitment_parameters( - obj: LeanAiurCommitmentParameters, + obj: &LeanAiurCommitmentParameters, ) -> CommitmentParameters { let ctor = obj.as_ctor(); CommitmentParameters { - log_blowup: lean_unbox_nat_as_usize(ctor.get(0)), - cap_height: lean_unbox_nat_as_usize(ctor.get(1)), + log_blowup: lean_unbox_nat_as_usize(&ctor.get(0)), + cap_height: lean_unbox_nat_as_usize(&ctor.get(1)), } } -fn decode_fri_parameters(obj: LeanAiurFriParameters) -> FriParameters { +fn decode_fri_parameters( + obj: &LeanAiurFriParameters, +) -> FriParameters { let ctor = obj.as_ctor(); FriParameters { - log_final_poly_len: lean_unbox_nat_as_usize(ctor.get(0)), - max_log_arity: lean_unbox_nat_as_usize(ctor.get(1)), - num_queries: lean_unbox_nat_as_usize(ctor.get(2)), - commit_proof_of_work_bits: lean_unbox_nat_as_usize(ctor.get(3)), - query_proof_of_work_bits: lean_unbox_nat_as_usize(ctor.get(4)), + log_final_poly_len: lean_unbox_nat_as_usize(&ctor.get(0)), + max_log_arity: lean_unbox_nat_as_usize(&ctor.get(1)), + num_queries: lean_unbox_nat_as_usize(&ctor.get(2)), + commit_proof_of_work_bits: lean_unbox_nat_as_usize(&ctor.get(3)), + query_proof_of_work_bits: lean_unbox_nat_as_usize(&ctor.get(4)), } } -fn decode_io_buffer_map(arr: LeanArray) -> FxHashMap, IOKeyInfo> { +fn decode_io_buffer_map( + arr: &LeanArray>, +) -> FxHashMap, IOKeyInfo> { let mut map = FxHashMap::with_capacity_and_hasher(arr.len(), FxBuildHasher); for elt in arr.iter() { let pair = elt.as_ctor(); - let key = pair.get(0).as_array().map(lean_unbox_g); + let key = pair.get(0).as_array().map(|x| lean_unbox_g(&x)); let info_ctor = pair.get(1).as_ctor(); let info = IOKeyInfo { - idx: lean_unbox_nat_as_usize(info_ctor.get(0)), - len: lean_unbox_nat_as_usize(info_ctor.get(1)), + idx: lean_unbox_nat_as_usize(&info_ctor.get(0)), + len: lean_unbox_nat_as_usize(&info_ctor.get(1)), }; map.insert(key, info); } diff --git a/src/ffi/aiur/toplevel.rs b/src/ffi/aiur/toplevel.rs index 083cb147..4c41ff5b 100644 --- a/src/ffi/aiur/toplevel.rs +++ b/src/ffi/aiur/toplevel.rs @@ -1,6 +1,6 @@ use multi_stark::p3_field::PrimeCharacteristicRing; -use lean_ffi::object::{LeanCtor, LeanObject}; +use lean_ffi::object::{LeanBorrowed, LeanCtor, LeanRef}; use crate::{ FxIndexMap, @@ -13,11 +13,11 @@ use crate::{ use crate::ffi::aiur::{lean_unbox_g, lean_unbox_nat_as_usize}; -fn decode_vec_val_idx(obj: LeanObject) -> Vec { - obj.as_array().map(lean_unbox_nat_as_usize) +fn decode_vec_val_idx(obj: LeanBorrowed<'_>) -> Vec { + obj.as_array().map(|x| lean_unbox_nat_as_usize(&x)) } -fn decode_op(ctor: LeanCtor) -> Op { +fn decode_op(ctor: LeanCtor>) -> Op { match ctor.tag() { 0 => { let [const_val] = ctor.objs::<1>(); @@ -25,25 +25,25 @@ fn decode_op(ctor: LeanCtor) -> Op { }, 1 => { let [a, b] = ctor.objs::<2>(); - Op::Add(lean_unbox_nat_as_usize(a), lean_unbox_nat_as_usize(b)) + Op::Add(lean_unbox_nat_as_usize(&a), lean_unbox_nat_as_usize(&b)) }, 2 => { let [a, b] = ctor.objs::<2>(); - Op::Sub(lean_unbox_nat_as_usize(a), lean_unbox_nat_as_usize(b)) + Op::Sub(lean_unbox_nat_as_usize(&a), lean_unbox_nat_as_usize(&b)) }, 3 => { let [a, b] = ctor.objs::<2>(); - Op::Mul(lean_unbox_nat_as_usize(a), lean_unbox_nat_as_usize(b)) + Op::Mul(lean_unbox_nat_as_usize(&a), lean_unbox_nat_as_usize(&b)) }, 4 => { let [a] = ctor.objs::<1>(); - Op::EqZero(lean_unbox_nat_as_usize(a)) + Op::EqZero(lean_unbox_nat_as_usize(&a)) }, 5 => { let [fun_idx, val_idxs, output_size] = ctor.objs::<3>(); - let fun_idx = lean_unbox_nat_as_usize(fun_idx); + let fun_idx = lean_unbox_nat_as_usize(&fun_idx); let val_idxs = decode_vec_val_idx(val_idxs); - let output_size = lean_unbox_nat_as_usize(output_size); + let output_size = lean_unbox_nat_as_usize(&output_size); Op::Call(fun_idx, val_idxs, output_size) }, 6 => { @@ -52,7 +52,10 @@ fn decode_op(ctor: LeanCtor) -> Op { }, 7 => { let [width, val_idx] = ctor.objs::<2>(); - Op::Load(lean_unbox_nat_as_usize(width), lean_unbox_nat_as_usize(val_idx)) + Op::Load( + lean_unbox_nat_as_usize(&width), + lean_unbox_nat_as_usize(&val_idx), + ) }, 8 => { let [a, b] = ctor.objs::<2>(); @@ -66,13 +69,13 @@ fn decode_op(ctor: LeanCtor) -> Op { let [key, idx, len] = ctor.objs::<3>(); Op::IOSetInfo( decode_vec_val_idx(key), - lean_unbox_nat_as_usize(idx), - lean_unbox_nat_as_usize(len), + lean_unbox_nat_as_usize(&idx), + lean_unbox_nat_as_usize(&len), ) }, 11 => { let [idx, len] = ctor.objs::<2>(); - Op::IORead(lean_unbox_nat_as_usize(idx), lean_unbox_nat_as_usize(len)) + Op::IORead(lean_unbox_nat_as_usize(&idx), lean_unbox_nat_as_usize(&len)) }, 12 => { let [data] = ctor.objs::<1>(); @@ -80,42 +83,42 @@ fn decode_op(ctor: LeanCtor) -> Op { }, 13 => { let [byte] = ctor.objs::<1>(); - Op::U8BitDecomposition(lean_unbox_nat_as_usize(byte)) + Op::U8BitDecomposition(lean_unbox_nat_as_usize(&byte)) }, 14 => { let [byte] = ctor.objs::<1>(); - Op::U8ShiftLeft(lean_unbox_nat_as_usize(byte)) + Op::U8ShiftLeft(lean_unbox_nat_as_usize(&byte)) }, 15 => { let [byte] = ctor.objs::<1>(); - Op::U8ShiftRight(lean_unbox_nat_as_usize(byte)) + Op::U8ShiftRight(lean_unbox_nat_as_usize(&byte)) }, 16 => { - let [i, j] = ctor.objs::<2>().map(lean_unbox_nat_as_usize); + let [i, j] = ctor.objs::<2>().map(|x| lean_unbox_nat_as_usize(&x)); Op::U8Xor(i, j) }, 17 => { - let [i, j] = ctor.objs::<2>().map(lean_unbox_nat_as_usize); + let [i, j] = ctor.objs::<2>().map(|x| lean_unbox_nat_as_usize(&x)); Op::U8Add(i, j) }, 18 => { - let [i, j] = ctor.objs::<2>().map(lean_unbox_nat_as_usize); + let [i, j] = ctor.objs::<2>().map(|x| lean_unbox_nat_as_usize(&x)); Op::U8Sub(i, j) }, 19 => { - let [i, j] = ctor.objs::<2>().map(lean_unbox_nat_as_usize); + let [i, j] = ctor.objs::<2>().map(|x| lean_unbox_nat_as_usize(&x)); Op::U8And(i, j) }, 20 => { - let [i, j] = ctor.objs::<2>().map(lean_unbox_nat_as_usize); + let [i, j] = ctor.objs::<2>().map(|x| lean_unbox_nat_as_usize(&x)); Op::U8Or(i, j) }, 21 => { - let [i, j] = ctor.objs::<2>().map(lean_unbox_nat_as_usize); + let [i, j] = ctor.objs::<2>().map(|x| lean_unbox_nat_as_usize(&x)); Op::U8LessThan(i, j) }, 22 => { - let [i, j] = ctor.objs::<2>().map(lean_unbox_nat_as_usize); + let [i, j] = ctor.objs::<2>().map(|x| lean_unbox_nat_as_usize(&x)); Op::U32LessThan(i, j) }, 23 => { @@ -125,7 +128,7 @@ fn decode_op(ctor: LeanCtor) -> Op { None } else { let inner_ctor = idxs_obj.as_ctor(); - Some(inner_ctor.get(0).as_array().map(lean_unbox_nat_as_usize)) + Some(inner_ctor.get(0).as_array().map(|x| lean_unbox_nat_as_usize(&x))) }; Op::Debug(label, idxs) }, @@ -133,18 +136,18 @@ fn decode_op(ctor: LeanCtor) -> Op { } } -fn decode_g_block_pair(ctor: LeanCtor) -> (G, Block) { +fn decode_g_block_pair(ctor: LeanCtor>) -> (G, Block) { let [g_obj, block_obj] = ctor.objs::<2>(); - let g = lean_unbox_g(g_obj); + let g = lean_unbox_g(&g_obj); let block = decode_block(block_obj.as_ctor()); (g, block) } -fn decode_ctrl(ctor: LeanCtor) -> Ctrl { +fn decode_ctrl(ctor: LeanCtor>) -> Ctrl { match ctor.tag() { 0 => { let [val_idx_obj, cases_obj, default_obj] = ctor.objs::<3>(); - let val_idx = lean_unbox_nat_as_usize(val_idx_obj); + let val_idx = lean_unbox_nat_as_usize(&val_idx_obj); let vec_cases = cases_obj.as_array().map(|o| decode_g_block_pair(o.as_ctor())); let cases = FxIndexMap::from_iter(vec_cases); @@ -159,7 +162,7 @@ fn decode_ctrl(ctor: LeanCtor) -> Ctrl { }, 1 => { let [sel_idx_obj, val_idxs_obj] = ctor.objs::<2>(); - let sel_idx = lean_unbox_nat_as_usize(sel_idx_obj); + let sel_idx = lean_unbox_nat_as_usize(&sel_idx_obj); let val_idxs = decode_vec_val_idx(val_idxs_obj); Ctrl::Return(sel_idx, val_idxs) }, @@ -167,26 +170,26 @@ fn decode_ctrl(ctor: LeanCtor) -> Ctrl { } } -fn decode_block(ctor: LeanCtor) -> Block { +fn decode_block(ctor: LeanCtor>) -> Block { let [ops_obj, ctrl_obj, min_sel_obj, max_sel_obj] = ctor.objs::<4>(); let ops = ops_obj.as_array().map(|o| decode_op(o.as_ctor())); let ctrl = decode_ctrl(ctrl_obj.as_ctor()); - let min_sel_included = lean_unbox_nat_as_usize(min_sel_obj); - let max_sel_excluded = lean_unbox_nat_as_usize(max_sel_obj); + let min_sel_included = lean_unbox_nat_as_usize(&min_sel_obj); + let max_sel_excluded = lean_unbox_nat_as_usize(&max_sel_obj); Block { ops, ctrl, min_sel_included, max_sel_excluded } } -fn decode_function_layout(ctor: LeanCtor) -> FunctionLayout { +fn decode_function_layout(ctor: LeanCtor>) -> FunctionLayout { let [input_size, selectors, auxiliaries, lookups] = ctor.objs::<4>(); FunctionLayout { - input_size: lean_unbox_nat_as_usize(input_size), - selectors: lean_unbox_nat_as_usize(selectors), - auxiliaries: lean_unbox_nat_as_usize(auxiliaries), - lookups: lean_unbox_nat_as_usize(lookups), + input_size: lean_unbox_nat_as_usize(&input_size), + selectors: lean_unbox_nat_as_usize(&selectors), + auxiliaries: lean_unbox_nat_as_usize(&auxiliaries), + lookups: lean_unbox_nat_as_usize(&lookups), } } -fn decode_function(ctor: LeanCtor) -> Function { +fn decode_function(ctor: LeanCtor>) -> Function { let [body_obj, layout_obj, unconstrained_obj] = ctor.objs::<3>(); let body = decode_block(body_obj.as_ctor()); let layout = decode_function_layout(layout_obj.as_ctor()); @@ -194,11 +197,14 @@ fn decode_function(ctor: LeanCtor) -> Function { Function { body, layout, unconstrained } } -pub(crate) fn decode_toplevel(obj: LeanAiurToplevel) -> Toplevel { +pub(crate) fn decode_toplevel( + obj: &LeanAiurToplevel, +) -> Toplevel { let ctor = obj.as_ctor(); let [functions_obj, memory_sizes_obj] = ctor.objs::<2>(); let functions = functions_obj.as_array().map(|o| decode_function(o.as_ctor())); - let memory_sizes = memory_sizes_obj.as_array().map(lean_unbox_nat_as_usize); + let memory_sizes = + memory_sizes_obj.as_array().map(|x| lean_unbox_nat_as_usize(&x)); Toplevel { functions, memory_sizes } } diff --git a/src/ffi/builder.rs b/src/ffi/builder.rs index e4fe8655..8876395b 100644 --- a/src/ffi/builder.rs +++ b/src/ffi/builder.rs @@ -3,6 +3,8 @@ use blake3::Hash; use rustc_hash::FxHashMap; +use lean_ffi::object::LeanOwned; + use crate::lean::{LeanIxExpr, LeanIxLevel, LeanIxName}; /// Cache for constructing Lean Ix types with deduplication. @@ -10,9 +12,9 @@ use crate::lean::{LeanIxExpr, LeanIxLevel, LeanIxName}; /// This struct maintains caches for names, levels, and expressions to avoid /// rebuilding the same Lean objects multiple times during environment construction. pub struct LeanBuildCache { - pub(crate) names: FxHashMap, - pub(crate) levels: FxHashMap, - pub(crate) exprs: FxHashMap, + pub(crate) names: FxHashMap>, + pub(crate) levels: FxHashMap>, + pub(crate) exprs: FxHashMap>, } impl LeanBuildCache { diff --git a/src/ffi/byte_array.rs b/src/ffi/byte_array.rs index 2831380e..ab53b2d8 100644 --- a/src/ffi/byte_array.rs +++ b/src/ffi/byte_array.rs @@ -1,8 +1,11 @@ -use lean_ffi::object::LeanByteArray; +use lean_ffi::object::{LeanBorrowed, LeanByteArray}; /// `@& ByteArray → @& ByteArray → Bool` /// Efficient implementation for `BEq ByteArray` #[unsafe(no_mangle)] -extern "C" fn rs_byte_array_beq(a: LeanByteArray, b: LeanByteArray) -> bool { +extern "C" fn rs_byte_array_beq( + a: LeanByteArray>, + b: LeanByteArray>, +) -> bool { a.as_bytes() == b.as_bytes() } diff --git a/src/ffi/compile.rs b/src/ffi/compile.rs index c859a031..bd8b09f0 100644 --- a/src/ffi/compile.rs +++ b/src/ffi/compile.rs @@ -7,7 +7,6 @@ //! - `rs_roundtrip_*`: roundtrip FFI tests for Lean↔Rust type conversions //! - `build_*` / `decode_*`: convert between Lean constructor layouts and Rust types -use std::collections::HashMap; use std::sync::Arc; use crate::ffi::ffi_io_guard; @@ -17,22 +16,23 @@ use crate::ix::condense::compute_sccs; use crate::ix::decompile::decompile_env; use crate::ix::env::Name; use crate::ix::graph::build_ref_graph; -use crate::ix::ixon::constant::{Constant as IxonConstant, ConstantInfo}; -use crate::ix::ixon::expr::Expr as IxonExpr; -use crate::ix::ixon::serialize::put_expr; +use crate::ix::ixon::constant::Constant as IxonConstant; use crate::ix::ixon::{Comm, ConstantMeta}; +#[cfg(feature = "test-ffi")] +use crate::ix::ixon::constant::ConstantInfo; +#[cfg(feature = "test-ffi")] +use crate::ix::ixon::expr::Expr as IxonExpr; use crate::lean::{ - LeanIxBlockCompareDetail, LeanIxBlockCompareResult, LeanIxCompileError, - LeanIxCompilePhases, LeanIxCondensedBlocks, LeanIxConstantInfo, - LeanIxDecompileError, LeanIxName, LeanIxRawEnvironment, LeanIxSerializeError, - LeanIxonRawBlob, LeanIxonRawComm, LeanIxonRawConst, LeanIxonRawEnv, - LeanIxonRawNameEntry, LeanIxonRawNamed, + LeanIxCompileError, LeanIxCondensedBlocks, LeanIxConstantInfo, + LeanIxDecompileError, LeanIxName, LeanIxRawEnvironment, + LeanIxSerializeError, LeanIxonRawBlob, LeanIxonRawComm, LeanIxonRawConst, + LeanIxonRawEnv, LeanIxonRawNameEntry, LeanIxonRawNamed, }; use lean_ffi::nat::Nat; use lean_ffi::object::LeanIOResult; use lean_ffi::object::{ - LeanArray, LeanByteArray, LeanCtor, LeanExcept, LeanList, LeanObject, - LeanString, + LeanArray, LeanBorrowed, LeanByteArray, LeanCtor, LeanExcept, LeanList, + LeanOwned, LeanRef, LeanString, }; use dashmap::DashMap; @@ -40,21 +40,32 @@ use dashmap::DashSet; use crate::ffi::builder::LeanBuildCache; use crate::ffi::ixon::env::decoded_to_ixon_env; -use crate::ffi::lean_env::{GlobalCache, decode_env, decode_name}; +use crate::ffi::lean_env::decode_env; use crate::lean::LeanIxAddress; +#[cfg(feature = "test-ffi")] +use std::collections::HashMap; +#[cfg(feature = "test-ffi")] +use crate::ix::ixon::serialize::put_expr; +#[cfg(feature = "test-ffi")] +use crate::lean::{ + LeanIxBlockCompareDetail, LeanIxBlockCompareResult, LeanIxCompilePhases, +}; +#[cfg(feature = "test-ffi")] +use crate::ffi::lean_env::{GlobalCache, decode_name}; + // ============================================================================= // Helper builders // ============================================================================= /// Build a Lean String from a Rust &str. -fn build_lean_string(s: &str) -> LeanString { +fn build_lean_string(s: &str) -> LeanString { LeanString::new(s) } /// Build a Lean Nat from a usize. -fn build_lean_nat_usize(n: usize) -> LeanObject { - LeanObject::from_nat_u64(n as u64) +fn build_lean_nat_usize(n: usize) -> LeanOwned { + LeanOwned::from_nat_u64(n as u64) } // ============================================================================= @@ -65,7 +76,7 @@ fn build_lean_nat_usize(n: usize) -> LeanObject { fn build_raw_const( addr: &Address, constant: &IxonConstant, -) -> LeanIxonRawConst { +) -> LeanIxonRawConst { LeanIxonRawConst::build_from_parts(addr, constant) } @@ -75,17 +86,17 @@ fn build_raw_named( name: &Name, addr: &Address, meta: &ConstantMeta, -) -> LeanIxonRawNamed { +) -> LeanIxonRawNamed { LeanIxonRawNamed::build_from_parts(cache, name, addr, meta) } /// Build RawBlob using type method. -fn build_raw_blob(addr: &Address, bytes: &[u8]) -> LeanIxonRawBlob { +fn build_raw_blob(addr: &Address, bytes: &[u8]) -> LeanIxonRawBlob { LeanIxonRawBlob::build_from_parts(addr, bytes) } /// Build RawComm using type method. -fn build_raw_comm(addr: &Address, comm: &Comm) -> LeanIxonRawComm { +fn build_raw_comm(addr: &Address, comm: &Comm) -> LeanIxonRawComm { LeanIxonRawComm::build_from_parts(addr, comm) } @@ -94,45 +105,39 @@ fn build_raw_comm(addr: &Address, comm: &Comm) -> LeanIxonRawComm { // ============================================================================= /// Round-trip a RustCondensedBlocks structure. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_rust_condensed_blocks( - obj: LeanIxCondensedBlocks, -) -> LeanIxCondensedBlocks { + obj: LeanIxCondensedBlocks>, +) -> LeanIxCondensedBlocks { let ctor = obj.as_ctor(); - let low_links = ctor.get(0); - let blocks = ctor.get(1); - let block_refs = ctor.get(2); - - low_links.inc_ref(); - blocks.inc_ref(); - block_refs.inc_ref(); + let low_links = ctor.get(0).to_owned_ref(); + let blocks = ctor.get(1).to_owned_ref(); + let block_refs = ctor.get(2).to_owned_ref(); let result = LeanCtor::alloc(0, 3, 0); result.set(0, low_links); result.set(1, blocks); result.set(2, block_refs); - LeanIxCondensedBlocks::new(*result) + LeanIxCondensedBlocks::new(result.into()) } /// Round-trip a RustCompilePhases structure. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_rust_compile_phases( - obj: LeanIxCompilePhases, -) -> LeanIxCompilePhases { + obj: LeanIxCompilePhases>, +) -> LeanIxCompilePhases { let ctor = obj.as_ctor(); - let raw_env = ctor.get(0); - let condensed = ctor.get(1); - let compile_env = ctor.get(2); - - raw_env.inc_ref(); - condensed.inc_ref(); - compile_env.inc_ref(); + let raw_env = ctor.get(0).to_owned_ref(); + let condensed = ctor.get(1).to_owned_ref(); + let compile_env = ctor.get(2).to_owned_ref(); let result = LeanCtor::alloc(0, 3, 0); result.set(0, raw_env); result.set(1, condensed); result.set(2, compile_env); - LeanIxCompilePhases::new(*result) + LeanIxCompilePhases::new(result.into()) } // ============================================================================= @@ -140,51 +145,51 @@ pub extern "C" fn rs_roundtrip_rust_compile_phases( // ============================================================================= /// Round-trip a BlockCompareResult. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_block_compare_result( - obj: LeanIxBlockCompareResult, -) -> LeanIxBlockCompareResult { + obj: LeanIxBlockCompareResult>, +) -> LeanIxBlockCompareResult { // Tags 0 (match) and 2 (notFound) have 0 fields → Lean represents as scalars - if obj.is_scalar() { - return obj; + if obj.inner().is_scalar() { + return LeanIxBlockCompareResult::new(obj.inner().to_owned_ref()); } let ctor = obj.as_ctor(); match ctor.tag() { 1 => { // mismatch: 0 obj, 24 scalar bytes (3 × u64) - let lean_size = ctor.scalar_u64(0, 0); - let rust_size = ctor.scalar_u64(0, 8); - let first_diff = ctor.scalar_u64(0, 16); + let lean_size = ctor.get_u64(0, 0); + let rust_size = ctor.get_u64(0, 8); + let first_diff = ctor.get_u64(0, 16); let out = LeanCtor::alloc(1, 0, 24); - out.set_scalar_u64(0, 0, lean_size); - out.set_scalar_u64(0, 8, rust_size); - out.set_scalar_u64(0, 16, first_diff); - LeanIxBlockCompareResult::new(*out) + out.set_u64(0, 0, lean_size); + out.set_u64(0, 8, rust_size); + out.set_u64(0, 16, first_diff); + LeanIxBlockCompareResult::new(out.into()) }, _ => unreachable!("Invalid BlockCompareResult tag: {}", ctor.tag()), } } /// Round-trip a BlockCompareDetail. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_block_compare_detail( - obj: LeanIxBlockCompareDetail, -) -> LeanIxBlockCompareDetail { + obj: LeanIxBlockCompareDetail>, +) -> LeanIxBlockCompareDetail { let ctor = obj.as_ctor(); - let result_ptr = ctor.get(0); - let lean_sharing_len = ctor.scalar_u64(1, 0); - let rust_sharing_len = ctor.scalar_u64(1, 8); + let lean_sharing_len = ctor.get_u64(1, 0); + let rust_sharing_len = ctor.get_u64(1, 8); - let result_obj = rs_roundtrip_block_compare_result( - LeanIxBlockCompareResult::new(result_ptr), - ); + let result_obj = + rs_roundtrip_block_compare_result(LeanIxBlockCompareResult(ctor.get(0))); let out = LeanCtor::alloc(0, 1, 16); out.set(0, result_obj); - out.set_scalar_u64(1, 0, lean_sharing_len); - out.set_scalar_u64(1, 8, rust_sharing_len); - LeanIxBlockCompareDetail::new(*out) + out.set_u64(1, 0, lean_sharing_len); + out.set_u64(1, 8, rust_sharing_len); + LeanIxBlockCompareDetail::new(out.into()) } // ============================================================================= @@ -194,8 +199,8 @@ pub extern "C" fn rs_roundtrip_block_compare_detail( /// FFI function to run the complete compilation pipeline and return all data. #[unsafe(no_mangle)] pub extern "C" fn rs_compile_env_full( - env_consts_ptr: LeanList, -) -> LeanIOResult { + env_consts_ptr: LeanList>, +) -> LeanIOResult { ffi_io_guard(std::panic::AssertUnwindSafe(|| { // Phase 1: Decode Lean environment let rust_env = decode_env(env_consts_ptr); @@ -254,9 +259,9 @@ pub extern "C" fn rs_compile_env_full( let block = LeanCtor::alloc(0, 2, 8); block.set(0, name_obj); block.set(1, ba); - block.set_scalar_u64(2, 0, *sharing_len as u64); + block.set_u64(2, 0, *sharing_len as u64); - blocks_arr.set(i, *block); + blocks_arr.set(i, block); } // Build nameToAddr array @@ -273,27 +278,29 @@ pub extern "C" fn rs_compile_env_full( entry_obj.set(0, name_obj); entry_obj.set(1, addr_ba); - name_to_addr_arr.set(i, *entry_obj); + name_to_addr_arr.set(i, entry_obj); } // Build RawCompiledEnv let compiled_obj = LeanCtor::alloc(0, 2, 0); - compiled_obj.set(0, *blocks_arr); - compiled_obj.set(1, *name_to_addr_arr); + compiled_obj.set(0, blocks_arr); + compiled_obj.set(1, name_to_addr_arr); // Build RustCompilationResult let result = LeanCtor::alloc(0, 3, 0); result.set(0, raw_env); result.set(1, condensed_obj); - result.set(2, *compiled_obj); + result.set(2, compiled_obj); - LeanIOResult::ok(*result) + LeanIOResult::ok(result) })) } /// FFI function to compile a Lean environment to serialized Ixon.Env bytes. #[unsafe(no_mangle)] -pub extern "C" fn rs_compile_env(env_consts_ptr: LeanList) -> LeanIOResult { +pub extern "C" fn rs_compile_env( + env_consts_ptr: LeanList>, +) -> LeanIOResult { ffi_io_guard(std::panic::AssertUnwindSafe(|| { let rust_env = decode_env(env_consts_ptr); let rust_env = Arc::new(rust_env); @@ -321,17 +328,20 @@ pub extern "C" fn rs_compile_env(env_consts_ptr: LeanList) -> LeanIOResult { /// Round-trip a RawEnv: decode from Lean, re-encode via builder. /// This performs a full decode/build cycle to verify FFI correctness. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_raw_env( - raw_env_obj: LeanIxonRawEnv, -) -> LeanIxonRawEnv { + raw_env_obj: LeanIxonRawEnv>, +) -> LeanIxonRawEnv { let env = raw_env_obj.decode(); LeanIxonRawEnv::build(&env) } /// FFI function to run all compilation phases and return combined results. #[unsafe(no_mangle)] -pub extern "C" fn rs_compile_phases(env_consts_ptr: LeanList) -> LeanIOResult { +pub extern "C" fn rs_compile_phases( + env_consts_ptr: LeanList>, +) -> LeanIOResult { ffi_io_guard(std::panic::AssertUnwindSafe(|| { let rust_env = decode_env(env_consts_ptr); let env_len = rust_env.len(); @@ -412,26 +422,26 @@ pub extern "C" fn rs_compile_phases(env_consts_ptr: LeanList) -> LeanIOResult { } let raw_ixon_env = LeanCtor::alloc(0, 5, 0); - raw_ixon_env.set(0, *consts_arr); - raw_ixon_env.set(1, *named_arr); - raw_ixon_env.set(2, *blobs_arr); - raw_ixon_env.set(3, *comms_arr); - raw_ixon_env.set(4, *names_arr); + raw_ixon_env.set(0, consts_arr); + raw_ixon_env.set(1, named_arr); + raw_ixon_env.set(2, blobs_arr); + raw_ixon_env.set(3, comms_arr); + raw_ixon_env.set(4, names_arr); let result = LeanCtor::alloc(0, 3, 0); result.set(0, raw_env); result.set(1, condensed_obj); - result.set(2, *raw_ixon_env); + result.set(2, raw_ixon_env); - LeanIOResult::ok(*result) + LeanIOResult::ok(result) })) } /// FFI function to compile a Lean environment to a RawEnv. #[unsafe(no_mangle)] pub extern "C" fn rs_compile_env_to_ixon( - env_consts_ptr: LeanList, -) -> LeanIOResult { + env_consts_ptr: LeanList>, +) -> LeanIOResult { ffi_io_guard(std::panic::AssertUnwindSafe(|| { let rust_env = decode_env(env_consts_ptr); let rust_env = Arc::new(rust_env); @@ -504,20 +514,20 @@ pub extern "C" fn rs_compile_env_to_ixon( } let result = LeanCtor::alloc(0, 5, 0); - result.set(0, *consts_arr); - result.set(1, *named_arr); - result.set(2, *blobs_arr); - result.set(3, *comms_arr); - result.set(4, *names_arr); - LeanIOResult::ok(*result) + result.set(0, consts_arr); + result.set(1, named_arr); + result.set(2, blobs_arr); + result.set(3, comms_arr); + result.set(4, names_arr); + LeanIOResult::ok(result) })) } /// FFI function to canonicalize environment to Ix.RawEnvironment. #[unsafe(no_mangle)] pub extern "C" fn rs_canonicalize_env_to_ix( - env_consts_ptr: LeanList, -) -> LeanIOResult { + env_consts_ptr: LeanList>, +) -> LeanIOResult { ffi_io_guard(std::panic::AssertUnwindSafe(|| { let rust_env = decode_env(env_consts_ptr); let mut cache = LeanBuildCache::with_capacity(rust_env.len()); @@ -530,6 +540,7 @@ pub extern "C" fn rs_canonicalize_env_to_ix( // RustCompiledEnv - Holds Rust compilation results for comparison // ============================================================================= +#[cfg(feature = "test-ffi")] /// Rust-compiled environment holding blocks indexed by low-link name. /// Each block is stored as serialized bytes for comparison with Lean output. pub struct RustCompiledEnv { @@ -545,8 +556,9 @@ pub struct RustCompiledEnv { /// FFI: Simple test to verify FFI round-trip works. /// Takes a Lean.Name and returns a magic number to verify the call succeeded. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -extern "C" fn rs_test_ffi_roundtrip(name_ptr: LeanObject) -> u64 { +extern "C" fn rs_test_ffi_roundtrip(name_ptr: LeanBorrowed<'_>) -> u64 { let global_cache = GlobalCache::default(); let name = decode_name(name_ptr, &global_cache); @@ -561,9 +573,10 @@ extern "C" fn rs_test_ffi_roundtrip(name_ptr: LeanObject) -> u64 { } /// FFI: Compile entire environment with Rust, returning a handle to RustCompiledEnv. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_compile_env_rust_first( - env_consts_ptr: LeanList, + env_consts_ptr: LeanList>, ) -> *mut RustCompiledEnv { // Decode Lean environment let lean_env = decode_env(env_consts_ptr); @@ -606,17 +619,21 @@ extern "C" fn rs_compile_env_rust_first( /// FFI: Compare a single block and return packed result. /// Returns a packed u64: high 32 bits = matches (1) or error code (0 = mismatch, 2 = not found) /// low 32 bits = first diff offset (if mismatch) +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_compare_block( rust_env: *const RustCompiledEnv, - lowlink_name: LeanObject, - lean_bytes: LeanByteArray, + lowlink_name: LeanOwned, + lean_bytes: LeanByteArray, ) -> u64 { if rust_env.is_null() { return 2u64 << 32; // not found } let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name, &global_cache); + let name = decode_name( + unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + &global_cache, + ); let rust_env = unsafe { &*rust_env }; let lean_data = lean_bytes.as_bytes(); @@ -647,6 +664,7 @@ extern "C" fn rs_compare_block( } /// FFI: Free a RustCompiledEnv. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_free_rust_env(rust_env: *mut RustCompiledEnv) { if !rust_env.is_null() { @@ -657,6 +675,7 @@ extern "C" fn rs_free_rust_env(rust_env: *mut RustCompiledEnv) { } /// FFI: Get the number of blocks in a RustCompiledEnv. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_get_rust_env_block_count( rust_env: *const RustCompiledEnv, @@ -669,16 +688,20 @@ extern "C" fn rs_get_rust_env_block_count( } /// FFI: Get Rust's compiled bytes length for a block. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_get_block_bytes_len( rust_env: *const RustCompiledEnv, - lowlink_name: LeanObject, + lowlink_name: LeanOwned, ) -> u64 { if rust_env.is_null() { return 0; } let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name, &global_cache); + let name = decode_name( + unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + &global_cache, + ); let rust_env = unsafe { &*rust_env }; @@ -689,17 +712,21 @@ extern "C" fn rs_get_block_bytes_len( } /// FFI: Copy Rust's compiled bytes into a pre-allocated Lean ByteArray. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_copy_block_bytes( rust_env: *const RustCompiledEnv, - lowlink_name: LeanObject, - dest: LeanByteArray, + lowlink_name: LeanOwned, + dest: LeanByteArray, ) { if rust_env.is_null() { return; } let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name, &global_cache); + let name = decode_name( + unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + &global_cache, + ); let rust_env = unsafe { &*rust_env }; @@ -713,16 +740,20 @@ extern "C" fn rs_copy_block_bytes( } /// FFI: Get Rust's sharing vector length for a block. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_get_block_sharing_len( rust_env: *const RustCompiledEnv, - lowlink_name: LeanObject, + lowlink_name: LeanOwned, ) -> u64 { if rust_env.is_null() { return 0; } let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name, &global_cache); + let name = decode_name( + unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + &global_cache, + ); let rust_env = unsafe { &*rust_env }; @@ -736,6 +767,7 @@ extern "C" fn rs_get_block_sharing_len( // Pre-sharing expression extraction FFI // ============================================================================= +#[cfg(feature = "test-ffi")] /// Frame for iterative unshare traversal. enum UnshareFrame<'a> { Visit(&'a Arc), @@ -746,6 +778,7 @@ enum UnshareFrame<'a> { BuildPrj(u64, u64), } +#[cfg(feature = "test-ffi")] /// Expand Share(idx) references in an expression using the sharing vector. /// This reconstructs the "pre-sharing" expression from the post-sharing /// representation. Uses iterative traversal to avoid stack overflow on deep @@ -832,17 +865,21 @@ fn unshare_expr( /// Each expression is serialized without sharing (Share nodes are expanded). /// /// Output format: [n_exprs:u64, len1:u64, expr1_bytes..., len2:u64, expr2_bytes..., ...] +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_get_pre_sharing_exprs( rust_env: *const RustCompiledEnv, - lowlink_name: LeanObject, - out_buf: LeanByteArray, + lowlink_name: LeanOwned, + out_buf: LeanByteArray, ) -> u64 { if rust_env.is_null() { return 0; } let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name, &global_cache); + let name = decode_name( + unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + &global_cache, + ); let rust_env = unsafe { &*rust_env }; @@ -934,16 +971,20 @@ extern "C" fn rs_get_pre_sharing_exprs( } /// FFI: Get the buffer length needed for pre-sharing expressions. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_get_pre_sharing_exprs_len( rust_env: *const RustCompiledEnv, - lowlink_name: LeanObject, + lowlink_name: LeanOwned, ) -> u64 { if rust_env.is_null() { return 0; } let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name, &global_cache); + let name = decode_name( + unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + &global_cache, + ); let rust_env = unsafe { &*rust_env }; @@ -994,17 +1035,21 @@ extern "C" fn rs_get_pre_sharing_exprs_len( /// FFI: Look up a constant's compiled address from RustCompiledEnv. /// Copies the 32-byte blake3 hash into the provided ByteArray. /// Returns 1 on success, 0 if name not found. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_lookup_const_addr( rust_env: *const RustCompiledEnv, - name_ptr: LeanObject, - out_addr: LeanByteArray, + name_ptr: LeanOwned, + out_addr: LeanByteArray, ) -> u64 { if rust_env.is_null() { return 0; } let global_cache = GlobalCache::default(); - let name = decode_name(name_ptr, &global_cache); + let name = decode_name( + unsafe { LeanBorrowed::from_raw(name_ptr.as_raw()) }, + &global_cache, + ); let rust_env = unsafe { &*rust_env }; @@ -1020,6 +1065,7 @@ extern "C" fn rs_lookup_const_addr( } /// FFI: Get the total number of compiled constants in RustCompiledEnv. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] extern "C" fn rs_get_compiled_const_count( rust_env: *const RustCompiledEnv, @@ -1037,7 +1083,7 @@ extern "C" fn rs_get_compiled_const_count( use crate::ix::ixon::error::{CompileError, DecompileError, SerializeError}; -impl LeanIxSerializeError { +impl LeanIxSerializeError { /// Build a Lean Ixon.SerializeError from a Rust SerializeError. /// /// Tags 0–6: @@ -1053,47 +1099,49 @@ impl LeanIxSerializeError { SerializeError::UnexpectedEof { expected } => { let ctor = LeanCtor::alloc(0, 1, 0); ctor.set(0, build_lean_string(expected)); - *ctor + ctor.into() }, SerializeError::InvalidTag { tag, context } => { let ctor = LeanCtor::alloc(1, 1, 1); ctor.set(0, build_lean_string(context)); - ctor.set_scalar_u8(1, 0, *tag); - *ctor + ctor.set_u8(1, 0, *tag); + ctor.into() }, SerializeError::InvalidFlag { flag, context } => { let ctor = LeanCtor::alloc(2, 1, 1); ctor.set(0, build_lean_string(context)); - ctor.set_scalar_u8(1, 0, *flag); - *ctor + ctor.set_u8(1, 0, *flag); + ctor.into() }, SerializeError::InvalidVariant { variant, context } => { let ctor = LeanCtor::alloc(3, 1, 8); ctor.set(0, build_lean_string(context)); - ctor.set_scalar_u64(1, 0, *variant); - *ctor + ctor.set_u64(1, 0, *variant); + ctor.into() }, SerializeError::InvalidBool { value } => { let ctor = LeanCtor::alloc(4, 0, 1); - ctor.set_scalar_u8(0, 0, *value); - *ctor + ctor.set_u8(0, 0, *value); + ctor.into() }, - SerializeError::AddressError => LeanObject::box_usize(5), + SerializeError::AddressError => LeanOwned::box_usize(5), SerializeError::InvalidShareIndex { idx, max } => { let ctor = LeanCtor::alloc(6, 1, 8); ctor.set(0, build_lean_nat_usize(*max)); - ctor.set_scalar_u64(1, 0, *idx); - *ctor + ctor.set_u64(1, 0, *idx); + ctor.into() }, }; Self::new(obj) } +} +impl LeanIxSerializeError { /// Decode a Lean Ixon.SerializeError to a Rust SerializeError. - pub fn decode(self) -> SerializeError { + pub fn decode(&self) -> SerializeError { // Tag 5 (addressError) has 0 fields → Lean represents as scalar - if self.is_scalar() { - let tag = self.unbox_usize(); + if self.inner().is_scalar() { + let tag = self.inner().unbox_usize(); assert_eq!(tag, 5, "Invalid scalar SerializeError tag: {}", tag); return SerializeError::AddressError; } @@ -1105,30 +1153,30 @@ impl LeanIxSerializeError { }, 1 => { let context = ctor.get(0).as_string().to_string(); - let tag_val = ctor.scalar_u8(1, 0); + let tag_val = ctor.get_u8(1, 0); SerializeError::InvalidTag { tag: tag_val, context } }, 2 => { let context = ctor.get(0).as_string().to_string(); - let flag = ctor.scalar_u8(1, 0); + let flag = ctor.get_u8(1, 0); SerializeError::InvalidFlag { flag, context } }, 3 => { let context = ctor.get(0).as_string().to_string(); - let variant = ctor.scalar_u64(1, 0); + let variant = ctor.get_u64(1, 0); SerializeError::InvalidVariant { variant, context } }, 4 => { - let value = ctor.scalar_u8(0, 0); + let value = ctor.get_u8(0, 0); SerializeError::InvalidBool { value } }, 5 => SerializeError::AddressError, 6 => { - let max = Nat::from_obj(ctor.get(0)) + let max = Nat::from_obj(&ctor.get(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); - let idx = ctor.scalar_u64(1, 0); + let idx = ctor.get_u64(1, 0); SerializeError::InvalidShareIndex { idx, max } }, _ => unreachable!("Invalid SerializeError tag: {}", ctor.tag()), @@ -1136,7 +1184,7 @@ impl LeanIxSerializeError { } } -impl LeanIxDecompileError { +impl LeanIxDecompileError { /// Build a Lean DecompileError from a Rust DecompileError. /// /// Layout for index variants (tags 0–4): @@ -1150,132 +1198,135 @@ impl LeanIxDecompileError { let ctor = LeanCtor::alloc(0, 2, 8); ctor.set(0, build_lean_nat_usize(*refs_len)); ctor.set(1, build_lean_string(constant)); - ctor.set_scalar_u64(2, 0, *idx); - *ctor + ctor.set_u64(2, 0, *idx); + ctor.into() }, DecompileError::InvalidUnivIndex { idx, univs_len, constant } => { let ctor = LeanCtor::alloc(1, 2, 8); ctor.set(0, build_lean_nat_usize(*univs_len)); ctor.set(1, build_lean_string(constant)); - ctor.set_scalar_u64(2, 0, *idx); - *ctor + ctor.set_u64(2, 0, *idx); + ctor.into() }, DecompileError::InvalidShareIndex { idx, max, constant } => { let ctor = LeanCtor::alloc(2, 2, 8); ctor.set(0, build_lean_nat_usize(*max)); ctor.set(1, build_lean_string(constant)); - ctor.set_scalar_u64(2, 0, *idx); - *ctor + ctor.set_u64(2, 0, *idx); + ctor.into() }, DecompileError::InvalidRecIndex { idx, ctx_size, constant } => { let ctor = LeanCtor::alloc(3, 2, 8); ctor.set(0, build_lean_nat_usize(*ctx_size)); ctor.set(1, build_lean_string(constant)); - ctor.set_scalar_u64(2, 0, *idx); - *ctor + ctor.set_u64(2, 0, *idx); + ctor.into() }, DecompileError::InvalidUnivVarIndex { idx, max, constant } => { let ctor = LeanCtor::alloc(4, 2, 8); ctor.set(0, build_lean_nat_usize(*max)); ctor.set(1, build_lean_string(constant)); - ctor.set_scalar_u64(2, 0, *idx); - *ctor + ctor.set_u64(2, 0, *idx); + ctor.into() }, DecompileError::MissingAddress(addr) => { let ctor = LeanCtor::alloc(5, 1, 0); ctor.set(0, LeanIxAddress::build(addr)); - *ctor + ctor.into() }, DecompileError::MissingMetadata(addr) => { let ctor = LeanCtor::alloc(6, 1, 0); ctor.set(0, LeanIxAddress::build(addr)); - *ctor + ctor.into() }, DecompileError::BlobNotFound(addr) => { let ctor = LeanCtor::alloc(7, 1, 0); ctor.set(0, LeanIxAddress::build(addr)); - *ctor + ctor.into() }, DecompileError::BadBlobFormat { addr, expected } => { let ctor = LeanCtor::alloc(8, 2, 0); ctor.set(0, LeanIxAddress::build(addr)); ctor.set(1, build_lean_string(expected)); - *ctor + ctor.into() }, DecompileError::BadConstantFormat { msg } => { let ctor = LeanCtor::alloc(9, 1, 0); ctor.set(0, build_lean_string(msg)); - *ctor + ctor.into() }, DecompileError::Serialize(se) => { let ctor = LeanCtor::alloc(10, 1, 0); ctor.set(0, LeanIxSerializeError::build(se)); - *ctor + ctor.into() }, }; Self::new(obj) } +} +impl LeanIxDecompileError { /// Decode a Lean DecompileError to a Rust DecompileError. - pub fn decode(self) -> DecompileError { + pub fn decode(&self) -> DecompileError { let ctor = self.as_ctor(); match ctor.tag() { 0 => { - let refs_len = Nat::from_obj(ctor.get(0)) + let refs_len = Nat::from_obj(&ctor.get(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); let constant = ctor.get(1).as_string().to_string(); - let idx = ctor.scalar_u64(2, 0); + let idx = ctor.get_u64(2, 0); DecompileError::InvalidRefIndex { idx, refs_len, constant } }, 1 => { - let univs_len = Nat::from_obj(ctor.get(0)) + let univs_len = Nat::from_obj(&ctor.get(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); let constant = ctor.get(1).as_string().to_string(); - let idx = ctor.scalar_u64(2, 0); + let idx = ctor.get_u64(2, 0); DecompileError::InvalidUnivIndex { idx, univs_len, constant } }, 2 => { - let max = Nat::from_obj(ctor.get(0)) + let max = Nat::from_obj(&ctor.get(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); let constant = ctor.get(1).as_string().to_string(); - let idx = ctor.scalar_u64(2, 0); + let idx = ctor.get_u64(2, 0); DecompileError::InvalidShareIndex { idx, max, constant } }, 3 => { - let ctx_size = Nat::from_obj(ctor.get(0)) + let ctx_size = Nat::from_obj(&ctor.get(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); let constant = ctor.get(1).as_string().to_string(); - let idx = ctor.scalar_u64(2, 0); + let idx = ctor.get_u64(2, 0); DecompileError::InvalidRecIndex { idx, ctx_size, constant } }, 4 => { - let max = Nat::from_obj(ctor.get(0)) + let max = Nat::from_obj(&ctor.get(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); let constant = ctor.get(1).as_string().to_string(); - let idx = ctor.scalar_u64(2, 0); + let idx = ctor.get_u64(2, 0); DecompileError::InvalidUnivVarIndex { idx, max, constant } }, - 5 => { - DecompileError::MissingAddress(LeanIxAddress::new(ctor.get(0)).decode()) - }, + 5 => DecompileError::MissingAddress( + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + ), 6 => DecompileError::MissingMetadata( - LeanIxAddress::new(ctor.get(0)).decode(), + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + ), + 7 => DecompileError::BlobNotFound( + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), ), - 7 => { - DecompileError::BlobNotFound(LeanIxAddress::new(ctor.get(0)).decode()) - }, 8 => { - let addr = LeanIxAddress::new(ctor.get(0)).decode(); + let addr = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); let expected = ctor.get(1).as_string().to_string(); DecompileError::BadBlobFormat { addr, expected } }, @@ -1283,15 +1334,15 @@ impl LeanIxDecompileError { let msg = ctor.get(0).as_string().to_string(); DecompileError::BadConstantFormat { msg } }, - 10 => DecompileError::Serialize( - LeanIxSerializeError::new(ctor.get(0)).decode(), - ), + 10 => { + DecompileError::Serialize(LeanIxSerializeError(ctor.get(0)).decode()) + }, _ => unreachable!("Invalid DecompileError tag: {}", ctor.tag()), } } } -impl LeanIxCompileError { +impl LeanIxCompileError { /// Build a Lean CompileError from a Rust CompileError. /// /// Tags 0–5: @@ -1306,49 +1357,51 @@ impl LeanIxCompileError { CompileError::MissingConstant { name } => { let ctor = LeanCtor::alloc(0, 1, 0); ctor.set(0, build_lean_string(name)); - *ctor + ctor.into() }, CompileError::MissingAddress(addr) => { let ctor = LeanCtor::alloc(1, 1, 0); ctor.set(0, LeanIxAddress::build(addr)); - *ctor + ctor.into() }, CompileError::InvalidMutualBlock { reason } => { let ctor = LeanCtor::alloc(2, 1, 0); ctor.set(0, build_lean_string(reason)); - *ctor + ctor.into() }, CompileError::UnsupportedExpr { desc } => { let ctor = LeanCtor::alloc(3, 1, 0); ctor.set(0, build_lean_string(desc)); - *ctor + ctor.into() }, CompileError::UnknownUnivParam { curr, param } => { let ctor = LeanCtor::alloc(4, 2, 0); ctor.set(0, build_lean_string(curr)); ctor.set(1, build_lean_string(param)); - *ctor + ctor.into() }, CompileError::Serialize(se) => { let ctor = LeanCtor::alloc(5, 1, 0); ctor.set(0, LeanIxSerializeError::build(se)); - *ctor + ctor.into() }, }; Self::new(obj) } +} +impl LeanIxCompileError { /// Decode a Lean CompileError to a Rust CompileError. - pub fn decode(self) -> CompileError { + pub fn decode(&self) -> CompileError { let ctor = self.as_ctor(); match ctor.tag() { 0 => { let name = ctor.get(0).as_string().to_string(); CompileError::MissingConstant { name } }, - 1 => { - CompileError::MissingAddress(LeanIxAddress::new(ctor.get(0)).decode()) - }, + 1 => CompileError::MissingAddress( + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + ), 2 => { let reason = ctor.get(0).as_string().to_string(); CompileError::InvalidMutualBlock { reason } @@ -1362,37 +1415,38 @@ impl LeanIxCompileError { let param = ctor.get(1).as_string().to_string(); CompileError::UnknownUnivParam { curr, param } }, - 5 => { - CompileError::Serialize(LeanIxSerializeError::new(ctor.get(0)).decode()) - }, + 5 => CompileError::Serialize(LeanIxSerializeError(ctor.get(0)).decode()), _ => unreachable!("Invalid CompileError tag: {}", ctor.tag()), } } } /// FFI: Round-trip a DecompileError: Lean → Rust → Lean. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_decompile_error( - obj: LeanIxDecompileError, -) -> LeanIxDecompileError { + obj: LeanIxDecompileError>, +) -> LeanIxDecompileError { let err = obj.decode(); LeanIxDecompileError::build(&err) } /// FFI: Round-trip a CompileError: Lean → Rust → Lean. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_compile_error( - obj: LeanIxCompileError, -) -> LeanIxCompileError { + obj: LeanIxCompileError>, +) -> LeanIxCompileError { let err = obj.decode(); LeanIxCompileError::build(&err) } /// FFI: Round-trip a SerializeError: Lean → Rust → Lean. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_serialize_error( - obj: LeanIxSerializeError, -) -> LeanIxSerializeError { + obj: LeanIxSerializeError>, +) -> LeanIxSerializeError { let err = obj.decode(); LeanIxSerializeError::build(&err) } @@ -1403,7 +1457,9 @@ pub extern "C" fn rs_roundtrip_serialize_error( /// FFI: Decompile an Ixon.RawEnv → Except DecompileError (Array (Ix.Name × Ix.ConstantInfo)). Pure. #[unsafe(no_mangle)] -pub extern "C" fn rs_decompile_env(raw_env_obj: LeanIxonRawEnv) -> LeanExcept { +pub extern "C" fn rs_decompile_env( + raw_env_obj: LeanIxonRawEnv, +) -> LeanExcept { let decoded = raw_env_obj.decode(); let env = decoded_to_ixon_env(&decoded); @@ -1427,7 +1483,7 @@ pub extern "C" fn rs_decompile_env(raw_env_obj: LeanIxonRawEnv) -> LeanExcept { let pair = LeanCtor::alloc(0, 2, 0); pair.set(0, name_obj); pair.set(1, info_obj); - arr.set(i, *pair); + arr.set(i, pair); } LeanExcept::ok(arr) diff --git a/src/ffi/graph.rs b/src/ffi/graph.rs index 1a3a0d7e..c3ddf11d 100644 --- a/src/ffi/graph.rs +++ b/src/ffi/graph.rs @@ -6,7 +6,9 @@ use crate::ffi::ffi_io_guard; use crate::ix::condense::compute_sccs; use crate::ix::graph::build_ref_graph; use crate::lean::LeanIxCondensedBlocks; -use lean_ffi::object::{LeanArray, LeanCtor, LeanIOResult, LeanList}; +use lean_ffi::object::{ + LeanArray, LeanBorrowed, LeanCtor, LeanIOResult, LeanList, LeanOwned, +}; use crate::ffi::builder::LeanBuildCache; use crate::ffi::lean_env::decode_env; @@ -16,7 +18,7 @@ use crate::lean::LeanIxName; pub fn build_ref_graph_array( cache: &mut LeanBuildCache, refs: &crate::ix::graph::RefMap, -) -> LeanArray { +) -> LeanArray { let arr = LeanArray::alloc(refs.len()); for (i, (name, ref_set)) in refs.iter().enumerate() { let name_obj = LeanIxName::build(cache, name); @@ -29,13 +31,13 @@ pub fn build_ref_graph_array( let pair = LeanCtor::alloc(0, 2, 0); pair.set(0, name_obj); - pair.set(1, *refs_arr); - arr.set(i, *pair); + pair.set(1, refs_arr); + arr.set(i, pair); } arr } -impl LeanIxCondensedBlocks { +impl LeanIxCondensedBlocks { /// Build a RustCondensedBlocks structure. pub fn build( cache: &mut LeanBuildCache, @@ -49,7 +51,7 @@ impl LeanIxCondensedBlocks { let pair = LeanCtor::alloc(0, 2, 0); pair.set(0, name_obj); pair.set(1, low_link_obj); - low_links_arr.set(i, *pair); + low_links_arr.set(i, pair); } // Build blocks: Array (Ix.Name × Array Ix.Name) @@ -63,8 +65,8 @@ impl LeanIxCondensedBlocks { } let pair = LeanCtor::alloc(0, 2, 0); pair.set(0, name_obj); - pair.set(1, *block_names_arr); - blocks_arr.set(i, *pair); + pair.set(1, block_names_arr); + blocks_arr.set(i, pair); } // Build blockRefs: Array (Ix.Name × Array Ix.Name) @@ -78,16 +80,16 @@ impl LeanIxCondensedBlocks { } let pair = LeanCtor::alloc(0, 2, 0); pair.set(0, name_obj); - pair.set(1, *refs_arr); - block_refs_arr.set(i, *pair); + pair.set(1, refs_arr); + block_refs_arr.set(i, pair); } // Build RustCondensedBlocks structure (3 fields) let result = LeanCtor::alloc(0, 3, 0); - result.set(0, *low_links_arr); - result.set(1, *blocks_arr); - result.set(2, *block_refs_arr); - Self::new(*result) + result.set(0, low_links_arr); + result.set(1, blocks_arr); + result.set(2, block_refs_arr); + Self::new(result.into()) } } @@ -97,7 +99,9 @@ impl LeanIxCondensedBlocks { /// FFI function to build a reference graph from a Lean environment. #[unsafe(no_mangle)] -pub extern "C" fn rs_build_ref_graph(env_consts_ptr: LeanList) -> LeanIOResult { +pub extern "C" fn rs_build_ref_graph( + env_consts_ptr: LeanList>, +) -> LeanIOResult { ffi_io_guard(std::panic::AssertUnwindSafe(|| { let rust_env = decode_env(env_consts_ptr); let rust_env = Arc::new(rust_env); @@ -110,7 +114,9 @@ pub extern "C" fn rs_build_ref_graph(env_consts_ptr: LeanList) -> LeanIOResult { /// FFI function to compute SCCs from a Lean environment. #[unsafe(no_mangle)] -pub extern "C" fn rs_compute_sccs(env_consts_ptr: LeanList) -> LeanIOResult { +pub extern "C" fn rs_compute_sccs( + env_consts_ptr: LeanList>, +) -> LeanIOResult { ffi_io_guard(std::panic::AssertUnwindSafe(|| { let rust_env = decode_env(env_consts_ptr); let rust_env = Arc::new(rust_env); diff --git a/src/ffi/iroh.rs b/src/ffi/iroh.rs index 6ccce722..a8038b9a 100644 --- a/src/ffi/iroh.rs +++ b/src/ffi/iroh.rs @@ -1,5 +1,6 @@ use lean_ffi::object::{ - LeanArray, LeanByteArray, LeanCtor, LeanExcept, LeanString, + LeanArray, LeanBorrowed, LeanByteArray, LeanCtor, LeanExcept, LeanOwned, + LeanRef, LeanString, }; use crate::iroh::common::{GetRequest, PutRequest, Request, Response}; @@ -12,7 +13,7 @@ lean_ffi::lean_domain_type! { LeanGetResponse; } -impl LeanPutResponse { +impl LeanPutResponse { /// Build from `message` and `hash` strings. /// /// ```lean @@ -24,11 +25,11 @@ impl LeanPutResponse { let ctor = LeanCtor::alloc(0, 2, 0); ctor.set(0, LeanString::new(message)); ctor.set(1, LeanString::new(hash)); - Self::new(*ctor) + Self::new(ctor.into()) } } -impl LeanGetResponse { +impl LeanGetResponse { /// Build from `message`, `hash`, and raw `bytes`. /// /// ```lean @@ -42,18 +43,18 @@ impl LeanGetResponse { ctor.set(0, LeanString::new(message)); ctor.set(1, LeanString::new(hash)); ctor.set(2, LeanByteArray::from_bytes(bytes)); - Self::new(*ctor) + Self::new(ctor.into()) } } /// `Iroh.Connect.putBytes' : @& String → @& Array String → @& String → @& String → Except String PutResponse` #[unsafe(no_mangle)] extern "C" fn rs_iroh_put( - node_id: LeanString, - addrs: LeanArray, - relay_url: LeanString, - input: LeanString, -) -> LeanExcept { + node_id: LeanString>, + addrs: LeanArray>, + relay_url: LeanString>, + input: LeanString>, +) -> LeanExcept { let node_id = node_id.to_string(); let addrs: Vec = addrs.map(|x| x.as_string().to_string()); let relay_url = relay_url.to_string(); @@ -79,11 +80,11 @@ extern "C" fn rs_iroh_put( /// `Iroh.Connect.getBytes' : @& String → @& Array String → @& String → @& String → Except String GetResponse` #[unsafe(no_mangle)] extern "C" fn rs_iroh_get( - node_id: LeanString, - addrs: LeanArray, - relay_url: LeanString, - hash: LeanString, -) -> LeanExcept { + node_id: LeanString>, + addrs: LeanArray>, + relay_url: LeanString>, + hash: LeanString>, +) -> LeanExcept { let node_id = node_id.to_string(); let addrs: Vec = addrs.map(|x| x.as_string().to_string()); let relay_url = relay_url.to_string(); @@ -109,12 +110,12 @@ extern "C" fn rs_iroh_get( /// `Iroh.Serve.serve' : Unit → Except String Unit` #[unsafe(no_mangle)] -extern "C" fn rs_iroh_serve() -> LeanExcept { +extern "C" fn rs_iroh_serve() -> LeanExcept { let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime"); match rt.block_on(server::serve()) { - Ok(()) => LeanExcept::ok(0), + Ok(()) => LeanExcept::ok(LeanOwned::box_usize(0)), Err(err) => LeanExcept::error_string(&err.to_string()), } } diff --git a/src/ffi/ix.rs b/src/ffi/ix.rs index c205d33e..2973c3ba 100644 --- a/src/ffi/ix.rs +++ b/src/ffi/ix.rs @@ -16,11 +16,3 @@ pub mod env; pub mod expr; pub mod level; pub mod name; - -pub use address::*; -pub use constant::*; -pub use data::*; -pub use env::*; -pub use expr::*; -pub use level::*; -pub use name::*; diff --git a/src/ffi/ix/address.rs b/src/ffi/ix/address.rs index 9531e9ba..f389f8d1 100644 --- a/src/ffi/ix/address.rs +++ b/src/ffi/ix/address.rs @@ -4,9 +4,9 @@ use crate::ix::address::Address; use crate::lean::LeanIxAddress; -use lean_ffi::object::{LeanArray, LeanByteArray}; +use lean_ffi::object::{LeanArray, LeanBorrowed, LeanByteArray, LeanOwned}; -impl LeanIxAddress { +impl LeanIxAddress { /// Build an Ix.Address from a blake3::Hash. pub fn build_from_hash(hash: &blake3::Hash) -> Self { LeanByteArray::from_bytes(hash.as_bytes()).into() @@ -18,32 +18,37 @@ impl LeanIxAddress { } /// Build an Array of Addresses. - pub fn build_array(addrs: &[Address]) -> LeanArray { + pub fn build_array(addrs: &[Address]) -> LeanArray { let arr = LeanArray::alloc(addrs.len()); for (i, addr) in addrs.iter().enumerate() { arr.set(i, Self::build(addr)); } arr } +} +impl LeanIxAddress { /// Decode a ByteArray (Address) to Address. - pub fn decode(self) -> Address { + pub fn decode(&self) -> Address { Address::from_slice(&self.as_bytes()[..32]) .expect("Address should be 32 bytes") } +} +impl LeanIxAddress> { /// Decode Array Address. - pub fn decode_array(obj: LeanArray) -> Vec
{ - obj.map(|x| LeanIxAddress::new(x).decode()) + pub fn decode_array(obj: LeanArray>) -> Vec
{ + obj.map(|x| LeanIxAddress::from_borrowed(x.as_byte_array()).decode()) } } /// Round-trip an Ix.Address: decode ByteArray, re-encode. /// Address = { hash : ByteArray } - single field struct, so UNBOXED to ByteArray directly +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ix_address( - addr: LeanIxAddress, -) -> LeanIxAddress { + addr: LeanIxAddress>, +) -> LeanIxAddress { let decoded = addr.decode(); LeanIxAddress::build(&decoded) } diff --git a/src/ffi/ix/constant.rs b/src/ffi/ix/constant.rs index 2fa77b47..ca1758b2 100644 --- a/src/ffi/ix/constant.rs +++ b/src/ffi/ix/constant.rs @@ -20,7 +20,9 @@ use crate::lean::{ LeanIxRecursorRule, LeanIxReducibilityHints, }; use lean_ffi::nat::Nat; -use lean_ffi::object::{LeanArray, LeanCtor, LeanObject}; +use lean_ffi::object::{LeanArray, LeanCtor, LeanOwned, LeanRef}; +#[cfg(feature = "test-ffi")] +use lean_ffi::object::LeanBorrowed; use crate::ffi::builder::LeanBuildCache; @@ -28,7 +30,7 @@ use crate::ffi::builder::LeanBuildCache; // ConstantVal // ============================================================================= -impl LeanIxConstantVal { +impl LeanIxConstantVal { /// Build a Ix.ConstantVal structure. pub fn build(cache: &mut LeanBuildCache, cv: &ConstantVal) -> Self { // ConstantVal = { name : Name, levelParams : Array Name, type : Expr } @@ -40,17 +42,19 @@ impl LeanIxConstantVal { obj.set(0, name_obj); obj.set(1, level_params_obj); obj.set(2, type_obj); - Self::new(*obj) + Self::new(obj.into()) } +} +impl LeanIxConstantVal { /// Decode Ix.ConstantVal from Lean object. /// ConstantVal = { name : Name, levelParams : Array Name, type : Expr } - pub fn decode(self) -> ConstantVal { + pub fn decode(&self) -> ConstantVal { let ctor = self.as_ctor(); - let name = LeanIxName::new(ctor.get(0)).decode(); + let name = LeanIxName(ctor.get(0)).decode(); let level_params: Vec = - ctor.get(1).as_array().map(|x| LeanIxName::new(x).decode()); - let typ = LeanIxExpr::new(ctor.get(2)).decode(); + ctor.get(1).as_array().map(|x| LeanIxName(x).decode()); + let typ = LeanIxExpr(ctor.get(2)).decode(); ConstantVal { name, level_params, typ } } @@ -60,31 +64,33 @@ impl LeanIxConstantVal { // ReducibilityHints // ============================================================================= -impl LeanIxReducibilityHints { +impl LeanIxReducibilityHints { /// Build ReducibilityHints. /// NOTE: In Lean 4, 0-field constructors are boxed scalars when the inductive has /// other constructors with fields. So opaque and abbrev use box_usize. pub fn build(hints: &ReducibilityHints) -> Self { let obj = match hints { // | opaque -- tag 0, boxed as scalar - ReducibilityHints::Opaque => LeanObject::box_usize(0), + ReducibilityHints::Opaque => LeanOwned::box_usize(0), // | abbrev -- tag 1, boxed as scalar - ReducibilityHints::Abbrev => LeanObject::box_usize(1), + ReducibilityHints::Abbrev => LeanOwned::box_usize(1), // | regular (h : UInt32) -- tag 2, object constructor ReducibilityHints::Regular(h) => { // UInt32 is a scalar, stored inline - let obj = LeanCtor::alloc(2, 0, 4); - obj.set_scalar_u32(0, 0, *h); - *obj + let ctor = LeanCtor::alloc(2, 0, 4); + ctor.set_u32(0, 0, *h); + ctor.into() }, }; Self::new(obj) } +} +impl LeanIxReducibilityHints { /// Decode Lean.ReducibilityHints from Lean object. - pub fn decode(self) -> ReducibilityHints { - if self.is_scalar() { - let tag = self.as_ptr() as usize >> 1; + pub fn decode(&self) -> ReducibilityHints { + if self.inner().is_scalar() { + let tag = self.inner().unbox_usize(); match tag { 0 => return ReducibilityHints::Opaque, 1 => return ReducibilityHints::Abbrev, @@ -98,7 +104,7 @@ impl LeanIxReducibilityHints { 1 => ReducibilityHints::Abbrev, 2 => { // regular: 0 obj fields, 4 scalar bytes (UInt32) - ReducibilityHints::Regular(ctor.scalar_u32(0, 0)) + ReducibilityHints::Regular(ctor.get_u32(0, 0)) }, _ => panic!("Invalid ReducibilityHints tag: {}", ctor.tag()), } @@ -109,14 +115,14 @@ impl LeanIxReducibilityHints { // RecursorRule // ============================================================================= -impl LeanIxRecursorRule { +impl LeanIxRecursorRule { /// Decode Ix.RecursorRule from Lean object. - pub fn decode(self) -> RecursorRule { + pub fn decode(&self) -> RecursorRule { let ctor = self.as_ctor(); RecursorRule { - ctor: LeanIxName::new(ctor.get(0)).decode(), - n_fields: Nat::from_obj(ctor.get(1)), - rhs: LeanIxExpr::new(ctor.get(2)).decode(), + ctor: LeanIxName(ctor.get(0)).decode(), + n_fields: Nat::from_obj(&ctor.get(1)), + rhs: LeanIxExpr(ctor.get(2)).decode(), } } } @@ -125,12 +131,12 @@ impl LeanIxRecursorRule { // ConstantInfo // ============================================================================= -impl LeanIxRecursorRule { +impl LeanIxRecursorRule { /// Build an Array of RecursorRule. pub fn build_array( cache: &mut LeanBuildCache, rules: &[RecursorRule], - ) -> LeanArray { + ) -> LeanArray { let arr = LeanArray::alloc(rules.len()); for (i, rule) in rules.iter().enumerate() { // RecursorRule = { ctor : Name, nFields : Nat, rhs : Expr } @@ -149,21 +155,21 @@ impl LeanIxRecursorRule { } } -impl LeanIxConstantInfo { +impl LeanIxConstantInfo { /// Build a Ix.ConstantInfo from a Rust ConstantInfo. pub fn build(cache: &mut LeanBuildCache, info: &ConstantInfo) -> Self { - let result = match info { + let result: LeanOwned = match info { // | axiomInfo (v : AxiomVal) -- tag 0 ConstantInfo::AxiomInfo(v) => { // AxiomVal = { cnst : ConstantVal, isUnsafe : Bool } let cnst_obj = LeanIxConstantVal::build(cache, &v.cnst); let axiom_val = LeanCtor::alloc(0, 1, 1); axiom_val.set(0, cnst_obj); - axiom_val.set_scalar_u8(1, 0, v.is_unsafe as u8); + axiom_val.set_u8(1, 0, v.is_unsafe as u8); let obj = LeanCtor::alloc(0, 1, 0); obj.set(0, axiom_val); - *obj + obj.into() }, // | defnInfo (v : DefinitionVal) -- tag 1 ConstantInfo::DefnInfo(v) => { @@ -184,11 +190,11 @@ impl LeanIxConstantInfo { defn_val.set(1, value_obj); defn_val.set(2, hints_obj); defn_val.set(3, all_obj); - defn_val.set_scalar_u8(4, 0, safety_byte); + defn_val.set_u8(4, 0, safety_byte); let obj = LeanCtor::alloc(1, 1, 0); obj.set(0, defn_val); - *obj + obj.into() }, // | thmInfo (v : TheoremVal) -- tag 2 ConstantInfo::ThmInfo(v) => { @@ -204,7 +210,7 @@ impl LeanIxConstantInfo { let obj = LeanCtor::alloc(2, 1, 0); obj.set(0, thm_val); - *obj + obj.into() }, // | opaqueInfo (v : OpaqueVal) -- tag 3 ConstantInfo::OpaqueInfo(v) => { @@ -217,11 +223,11 @@ impl LeanIxConstantInfo { opaque_val.set(0, cnst_obj); opaque_val.set(1, value_obj); opaque_val.set(2, all_obj); - opaque_val.set_scalar_u8(3, 0, v.is_unsafe as u8); + opaque_val.set_u8(3, 0, v.is_unsafe as u8); let obj = LeanCtor::alloc(3, 1, 0); obj.set(0, opaque_val); - *obj + obj.into() }, // | quotInfo (v : QuotVal) -- tag 4 ConstantInfo::QuotInfo(v) => { @@ -237,11 +243,11 @@ impl LeanIxConstantInfo { let quot_val = LeanCtor::alloc(0, 1, 1); quot_val.set(0, cnst_obj); - quot_val.set_scalar_u8(1, 0, kind_byte); + quot_val.set_u8(1, 0, kind_byte); let obj = LeanCtor::alloc(4, 1, 0); obj.set(0, quot_val); - *obj + obj.into() }, // | inductInfo (v : InductiveVal) -- tag 5 ConstantInfo::InductInfo(v) => { @@ -261,13 +267,13 @@ impl LeanIxConstantInfo { induct_val.set(3, all_obj); induct_val.set(4, ctors_obj); induct_val.set(5, num_nested_obj); - induct_val.set_scalar_u8(6, 0, v.is_rec as u8); - induct_val.set_scalar_u8(6, 1, v.is_unsafe as u8); - induct_val.set_scalar_u8(6, 2, v.is_reflexive as u8); + induct_val.set_u8(6, 0, v.is_rec as u8); + induct_val.set_u8(6, 1, v.is_unsafe as u8); + induct_val.set_u8(6, 2, v.is_reflexive as u8); let obj = LeanCtor::alloc(5, 1, 0); obj.set(0, induct_val); - *obj + obj.into() }, // | ctorInfo (v : ConstructorVal) -- tag 6 ConstantInfo::CtorInfo(v) => { @@ -285,11 +291,11 @@ impl LeanIxConstantInfo { ctor_val.set(2, cidx_obj); ctor_val.set(3, num_params_obj); ctor_val.set(4, num_fields_obj); - ctor_val.set_scalar_u8(5, 0, v.is_unsafe as u8); + ctor_val.set_u8(5, 0, v.is_unsafe as u8); let obj = LeanCtor::alloc(6, 1, 0); obj.set(0, ctor_val); - *obj + obj.into() }, // | recInfo (v : RecursorVal) -- tag 7 ConstantInfo::RecInfo(v) => { @@ -311,35 +317,37 @@ impl LeanIxConstantInfo { rec_val.set(4, num_motives_obj); rec_val.set(5, num_minors_obj); rec_val.set(6, rules_obj); - rec_val.set_scalar_u8(7, 0, v.k as u8); - rec_val.set_scalar_u8(7, 1, v.is_unsafe as u8); + rec_val.set_u8(7, 0, v.k as u8); + rec_val.set_u8(7, 1, v.is_unsafe as u8); let obj = LeanCtor::alloc(7, 1, 0); obj.set(0, rec_val); - *obj + obj.into() }, }; Self::new(result) } +} +impl LeanIxConstantInfo { /// Decode Ix.ConstantInfo from Lean object. - pub fn decode(self) -> ConstantInfo { + pub fn decode(&self) -> ConstantInfo { let outer = self.as_ctor(); let inner_obj = outer.get(0); let inner = inner_obj.as_ctor(); match outer.tag() { 0 => { - let is_unsafe = inner.scalar_u8(1, 0) != 0; + let is_unsafe = inner.get_u8(1, 0) != 0; ConstantInfo::AxiomInfo(AxiomVal { - cnst: LeanIxConstantVal::new(inner.get(0)).decode(), + cnst: LeanIxConstantVal(inner.get(0)).decode(), is_unsafe, }) }, 1 => { - let safety_byte = inner.scalar_u8(4, 0); + let safety_byte = inner.get_u8(4, 0); let safety = match safety_byte { 0 => DefinitionSafety::Unsafe, 1 => DefinitionSafety::Safe, @@ -348,30 +356,30 @@ impl LeanIxConstantInfo { }; ConstantInfo::DefnInfo(DefinitionVal { - cnst: LeanIxConstantVal::new(inner.get(0)).decode(), - value: LeanIxExpr::new(inner.get(1)).decode(), - hints: LeanIxReducibilityHints::new(inner.get(2)).decode(), + cnst: LeanIxConstantVal(inner.get(0)).decode(), + value: LeanIxExpr(inner.get(1)).decode(), + hints: LeanIxReducibilityHints(inner.get(2)).decode(), safety, all: LeanIxName::decode_array(inner.get(3).as_array()), }) }, 2 => ConstantInfo::ThmInfo(TheoremVal { - cnst: LeanIxConstantVal::new(inner.get(0)).decode(), - value: LeanIxExpr::new(inner.get(1)).decode(), + cnst: LeanIxConstantVal(inner.get(0)).decode(), + value: LeanIxExpr(inner.get(1)).decode(), all: LeanIxName::decode_array(inner.get(2).as_array()), }), 3 => { - let is_unsafe = inner.scalar_u8(3, 0) != 0; + let is_unsafe = inner.get_u8(3, 0) != 0; ConstantInfo::OpaqueInfo(OpaqueVal { - cnst: LeanIxConstantVal::new(inner.get(0)).decode(), - value: LeanIxExpr::new(inner.get(1)).decode(), + cnst: LeanIxConstantVal(inner.get(0)).decode(), + value: LeanIxExpr(inner.get(1)).decode(), is_unsafe, all: LeanIxName::decode_array(inner.get(2).as_array()), }) }, 4 => { - let kind_byte = inner.scalar_u8(1, 0); + let kind_byte = inner.get_u8(1, 0); let kind = match kind_byte { 0 => QuotKind::Type, 1 => QuotKind::Ctor, @@ -381,53 +389,53 @@ impl LeanIxConstantInfo { }; ConstantInfo::QuotInfo(QuotVal { - cnst: LeanIxConstantVal::new(inner.get(0)).decode(), + cnst: LeanIxConstantVal(inner.get(0)).decode(), kind, }) }, 5 => { - let is_rec = inner.scalar_u8(6, 0) != 0; - let is_unsafe = inner.scalar_u8(6, 1) != 0; - let is_reflexive = inner.scalar_u8(6, 2) != 0; + let is_rec = inner.get_u8(6, 0) != 0; + let is_unsafe = inner.get_u8(6, 1) != 0; + let is_reflexive = inner.get_u8(6, 2) != 0; ConstantInfo::InductInfo(InductiveVal { - cnst: LeanIxConstantVal::new(inner.get(0)).decode(), - num_params: Nat::from_obj(inner.get(1)), - num_indices: Nat::from_obj(inner.get(2)), + cnst: LeanIxConstantVal(inner.get(0)).decode(), + num_params: Nat::from_obj(&inner.get(1)), + num_indices: Nat::from_obj(&inner.get(2)), all: LeanIxName::decode_array(inner.get(3).as_array()), ctors: LeanIxName::decode_array(inner.get(4).as_array()), - num_nested: Nat::from_obj(inner.get(5)), + num_nested: Nat::from_obj(&inner.get(5)), is_rec, is_unsafe, is_reflexive, }) }, 6 => { - let is_unsafe = inner.scalar_u8(5, 0) != 0; + let is_unsafe = inner.get_u8(5, 0) != 0; ConstantInfo::CtorInfo(ConstructorVal { - cnst: LeanIxConstantVal::new(inner.get(0)).decode(), - induct: LeanIxName::new(inner.get(1)).decode(), - cidx: Nat::from_obj(inner.get(2)), - num_params: Nat::from_obj(inner.get(3)), - num_fields: Nat::from_obj(inner.get(4)), + cnst: LeanIxConstantVal(inner.get(0)).decode(), + induct: LeanIxName(inner.get(1)).decode(), + cidx: Nat::from_obj(&inner.get(2)), + num_params: Nat::from_obj(&inner.get(3)), + num_fields: Nat::from_obj(&inner.get(4)), is_unsafe, }) }, 7 => { - let k = inner.scalar_u8(7, 0) != 0; - let is_unsafe = inner.scalar_u8(7, 1) != 0; + let k = inner.get_u8(7, 0) != 0; + let is_unsafe = inner.get_u8(7, 1) != 0; let rules: Vec = - inner.get(6).as_array().map(|x| LeanIxRecursorRule::new(x).decode()); + inner.get(6).as_array().map(|x| LeanIxRecursorRule(x).decode()); ConstantInfo::RecInfo(RecursorVal { - cnst: LeanIxConstantVal::new(inner.get(0)).decode(), + cnst: LeanIxConstantVal(inner.get(0)).decode(), all: LeanIxName::decode_array(inner.get(1).as_array()), - num_params: Nat::from_obj(inner.get(2)), - num_indices: Nat::from_obj(inner.get(3)), - num_motives: Nat::from_obj(inner.get(4)), - num_minors: Nat::from_obj(inner.get(5)), + num_params: Nat::from_obj(&inner.get(2)), + num_indices: Nat::from_obj(&inner.get(3)), + num_motives: Nat::from_obj(&inner.get(4)), + num_minors: Nat::from_obj(&inner.get(5)), rules, k, is_unsafe, @@ -439,10 +447,11 @@ impl LeanIxConstantInfo { } /// Round-trip an Ix.ConstantInfo: decode from Lean, re-encode via LeanBuildCache. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ix_constant_info( - info_ptr: LeanIxConstantInfo, -) -> LeanIxConstantInfo { + info_ptr: LeanIxConstantInfo>, +) -> LeanIxConstantInfo { let info = info_ptr.decode(); let mut cache = LeanBuildCache::new(); LeanIxConstantInfo::build(&mut cache, &info) diff --git a/src/ffi/ix/data.rs b/src/ffi/ix/data.rs index f2b9d3ec..42ff0a2a 100644 --- a/src/ffi/ix/data.rs +++ b/src/ffi/ix/data.rs @@ -8,32 +8,38 @@ use crate::lean::{ LeanIxSyntax, LeanIxSyntaxPreresolved, }; use lean_ffi::nat::Nat; -use lean_ffi::object::{LeanArray, LeanCtor, LeanString}; +use lean_ffi::object::{ + LeanArray, LeanCtor, LeanOwned, LeanRef, LeanString, +}; +#[cfg(feature = "test-ffi")] +use lean_ffi::object::LeanBorrowed; use crate::ffi::builder::LeanBuildCache; -impl LeanIxInt { +impl LeanIxInt { /// Build a Ix.Int (ofNat or negSucc). pub fn build(int: &Int) -> Self { match int { Int::OfNat(n) => { let obj = LeanCtor::alloc(0, 1, 0); obj.set(0, Nat::to_lean(n)); - Self::new(*obj) + Self::new(obj.into()) }, Int::NegSucc(n) => { let obj = LeanCtor::alloc(1, 1, 0); obj.set(0, Nat::to_lean(n)); - Self::new(*obj) + Self::new(obj.into()) }, } } +} +impl LeanIxInt { /// Decode Ix.Int from Lean object. /// Ix.Int: ofNat (tag 0, 1 field) | negSucc (tag 1, 1 field) - pub fn decode(self) -> Int { + pub fn decode(&self) -> Int { let ctor = self.as_ctor(); - let nat = Nat::from_obj(ctor.get(0)); + let nat = Nat::from_obj(&ctor.get(0)); match ctor.tag() { 0 => Int::OfNat(nat), 1 => Int::NegSucc(nat), @@ -42,28 +48,30 @@ impl LeanIxInt { } } -impl LeanIxSubstring { +impl LeanIxSubstring { /// Build a Ix.Substring. pub fn build(ss: &Substring) -> Self { let obj = LeanCtor::alloc(0, 3, 0); obj.set(0, LeanString::new(ss.str.as_str())); obj.set(1, Nat::to_lean(&ss.start_pos)); obj.set(2, Nat::to_lean(&ss.stop_pos)); - Self::new(*obj) + Self::new(obj.into()) } +} +impl LeanIxSubstring { /// Decode Ix.Substring. - pub fn decode(self) -> Substring { + pub fn decode(&self) -> Substring { let ctor = self.as_ctor(); Substring { str: ctor.get(0).as_string().to_string(), - start_pos: Nat::from_obj(ctor.get(1)), - stop_pos: Nat::from_obj(ctor.get(2)), + start_pos: Nat::from_obj(&ctor.get(1)), + stop_pos: Nat::from_obj(&ctor.get(2)), } } } -impl LeanIxSourceInfo { +impl LeanIxSourceInfo { /// Build a Ix.SourceInfo. pub fn build(si: &SourceInfo) -> Self { match si { @@ -74,24 +82,26 @@ impl LeanIxSourceInfo { obj.set(1, Nat::to_lean(pos)); obj.set(2, LeanIxSubstring::build(trailing)); obj.set(3, Nat::to_lean(end_pos)); - Self::new(*obj) + Self::new(obj.into()) }, // | synthetic (pos : Nat) (endPos : Nat) (canonical : Bool) -- tag 1 SourceInfo::Synthetic(pos, end_pos, canonical) => { let obj = LeanCtor::alloc(1, 2, 1); obj.set(0, Nat::to_lean(pos)); obj.set(1, Nat::to_lean(end_pos)); - obj.set_scalar_u8(2, 0, *canonical as u8); - Self::new(*obj) + obj.set_u8(2, 0, *canonical as u8); + Self::new(obj.into()) }, // | none -- tag 2 - SourceInfo::None => Self::new(*LeanCtor::alloc(2, 0, 0)), + SourceInfo::None => Self::new(LeanCtor::alloc(2, 0, 0).into()), } } +} +impl LeanIxSourceInfo { /// Decode Ix.SourceInfo. - pub fn decode(self) -> SourceInfo { - if self.is_scalar() { + pub fn decode(&self) -> SourceInfo { + if self.inner().is_scalar() { return SourceInfo::None; } let ctor = self.as_ctor(); @@ -99,19 +109,19 @@ impl LeanIxSourceInfo { 0 => { // original SourceInfo::Original( - LeanIxSubstring::new(ctor.get(0)).decode(), - Nat::from_obj(ctor.get(1)), - LeanIxSubstring::new(ctor.get(2)).decode(), - Nat::from_obj(ctor.get(3)), + LeanIxSubstring(ctor.get(0)).decode(), + Nat::from_obj(&ctor.get(1)), + LeanIxSubstring(ctor.get(2)).decode(), + Nat::from_obj(&ctor.get(3)), ) }, 1 => { // synthetic: 2 obj fields (pos, end_pos), 1 scalar byte (canonical) - let canonical = ctor.scalar_u8(2, 0) != 0; + let canonical = ctor.get_u8(2, 0) != 0; SourceInfo::Synthetic( - Nat::from_obj(ctor.get(0)), - Nat::from_obj(ctor.get(1)), + Nat::from_obj(&ctor.get(0)), + Nat::from_obj(&ctor.get(1)), canonical, ) }, @@ -121,7 +131,7 @@ impl LeanIxSourceInfo { } } -impl LeanIxSyntaxPreresolved { +impl LeanIxSyntaxPreresolved { /// Build a Ix.SyntaxPreresolved. pub fn build(cache: &mut LeanBuildCache, sp: &SyntaxPreresolved) -> Self { match sp { @@ -129,7 +139,7 @@ impl LeanIxSyntaxPreresolved { SyntaxPreresolved::Namespace(name) => { let obj = LeanCtor::alloc(0, 1, 0); obj.set(0, LeanIxName::build(cache, name)); - Self::new(*obj) + Self::new(obj.into()) }, // | decl (name : Name) (aliases : Array String) -- tag 1 SyntaxPreresolved::Decl(name, aliases) => { @@ -138,22 +148,24 @@ impl LeanIxSyntaxPreresolved { let obj = LeanCtor::alloc(1, 2, 0); obj.set(0, name_obj); obj.set(1, aliases_obj); - Self::new(*obj) + Self::new(obj.into()) }, } } +} +impl LeanIxSyntaxPreresolved { /// Decode Ix.SyntaxPreresolved. - pub fn decode(self) -> SyntaxPreresolved { + pub fn decode(&self) -> SyntaxPreresolved { let ctor = self.as_ctor(); match ctor.tag() { 0 => { // namespace - SyntaxPreresolved::Namespace(LeanIxName::new(ctor.get(0)).decode()) + SyntaxPreresolved::Namespace(LeanIxName(ctor.get(0)).decode()) }, 1 => { // decl - let name = LeanIxName::new(ctor.get(0)).decode(); + let name = LeanIxName(ctor.get(0)).decode(); let aliases: Vec = ctor.get(1).as_array().map(|obj| obj.as_string().to_string()); @@ -165,7 +177,7 @@ impl LeanIxSyntaxPreresolved { } /// Build an Array of Strings. -pub fn build_string_array(strings: &[String]) -> LeanArray { +pub fn build_string_array(strings: &[String]) -> LeanArray { let arr = LeanArray::alloc(strings.len()); for (i, s) in strings.iter().enumerate() { arr.set(i, LeanString::new(s.as_str())); @@ -173,12 +185,12 @@ pub fn build_string_array(strings: &[String]) -> LeanArray { arr } -impl LeanIxSyntax { +impl LeanIxSyntax { /// Build a Ix.Syntax. pub fn build(cache: &mut LeanBuildCache, syn: &Syntax) -> Self { match syn { // | missing -- tag 0 - Syntax::Missing => Self::new(*LeanCtor::alloc(0, 0, 0)), + Syntax::Missing => Self::new(LeanCtor::alloc(0, 0, 0).into()), // | node (info : SourceInfo) (kind : Name) (args : Array Syntax) -- tag 1 Syntax::Node(info, kind, args) => { let info_obj = LeanIxSourceInfo::build(info); @@ -188,7 +200,7 @@ impl LeanIxSyntax { obj.set(0, info_obj); obj.set(1, kind_obj); obj.set(2, args_obj); - Self::new(*obj) + Self::new(obj.into()) }, // | atom (info : SourceInfo) (val : String) -- tag 2 Syntax::Atom(info, val) => { @@ -196,7 +208,7 @@ impl LeanIxSyntax { let obj = LeanCtor::alloc(2, 2, 0); obj.set(0, info_obj); obj.set(1, LeanString::new(val.as_str())); - Self::new(*obj) + Self::new(obj.into()) }, // | ident (info : SourceInfo) (rawVal : Substring) (val : Name) (preresolved : Array SyntaxPreresolved) -- tag 3 Syntax::Ident(info, raw_val, val, preresolved) => { @@ -209,7 +221,7 @@ impl LeanIxSyntax { obj.set(1, raw_val_obj); obj.set(2, val_obj); obj.set(3, preresolved_obj); - Self::new(*obj) + Self::new(obj.into()) }, } } @@ -218,7 +230,7 @@ impl LeanIxSyntax { pub fn build_array( cache: &mut LeanBuildCache, items: &[Syntax], - ) -> LeanArray { + ) -> LeanArray { let arr = LeanArray::alloc(items.len()); for (i, item) in items.iter().enumerate() { arr.set(i, Self::build(cache, item)); @@ -230,17 +242,19 @@ impl LeanIxSyntax { fn build_preresolved_array( cache: &mut LeanBuildCache, items: &[SyntaxPreresolved], - ) -> LeanArray { + ) -> LeanArray { let arr = LeanArray::alloc(items.len()); for (i, item) in items.iter().enumerate() { arr.set(i, LeanIxSyntaxPreresolved::build(cache, item)); } arr } +} +impl LeanIxSyntax { /// Decode Ix.Syntax from a Lean object. - pub fn decode(self) -> Syntax { - if self.is_scalar() { + pub fn decode(&self) -> Syntax { + if self.inner().is_scalar() { return Syntax::Missing; } let ctor = self.as_ctor(); @@ -248,27 +262,25 @@ impl LeanIxSyntax { 0 => Syntax::Missing, 1 => { // node: info, kind, args - let info = LeanIxSourceInfo::new(ctor.get(0)).decode(); - let kind = LeanIxName::new(ctor.get(1)).decode(); + let info = LeanIxSourceInfo(ctor.get(0)).decode(); + let kind = LeanIxName(ctor.get(1)).decode(); let args: Vec = - ctor.get(2).as_array().map(|x| Self::new(x).decode()); + ctor.get(2).as_array().map(|x| LeanIxSyntax(x).decode()); Syntax::Node(info, kind, args) }, 2 => { // atom: info, val - let info = LeanIxSourceInfo::new(ctor.get(0)).decode(); + let info = LeanIxSourceInfo(ctor.get(0)).decode(); Syntax::Atom(info, ctor.get(1).as_string().to_string()) }, 3 => { // ident: info, rawVal, val, preresolved - let info = LeanIxSourceInfo::new(ctor.get(0)).decode(); - let raw_val = LeanIxSubstring::new(ctor.get(1)).decode(); - let val = LeanIxName::new(ctor.get(2)).decode(); - let preresolved: Vec = ctor - .get(3) - .as_array() - .map(|x| LeanIxSyntaxPreresolved::new(x).decode()); + let info = LeanIxSourceInfo(ctor.get(0)).decode(); + let raw_val = LeanIxSubstring(ctor.get(1)).decode(); + let val = LeanIxName(ctor.get(2)).decode(); + let preresolved: Vec = + ctor.get(3).as_array().map(|x| LeanIxSyntaxPreresolved(x).decode()); Syntax::Ident(info, raw_val, val, preresolved) }, @@ -277,54 +289,54 @@ impl LeanIxSyntax { } } -impl LeanIxDataValue { +impl LeanIxDataValue { /// Build Ix.DataValue. pub fn build(cache: &mut LeanBuildCache, dv: &DataValue) -> Self { match dv { DataValue::OfString(s) => { let obj = LeanCtor::alloc(0, 1, 0); obj.set(0, LeanString::new(s.as_str())); - Self::new(*obj) + Self::new(obj.into()) }, DataValue::OfBool(b) => { // 0 object fields, 1 scalar byte let obj = LeanCtor::alloc(1, 0, 1); - obj.set_scalar_u8(0, 0, *b as u8); - Self::new(*obj) + obj.set_u8(0, 0, *b as u8); + Self::new(obj.into()) }, DataValue::OfName(n) => { let obj = LeanCtor::alloc(2, 1, 0); obj.set(0, LeanIxName::build(cache, n)); - Self::new(*obj) + Self::new(obj.into()) }, DataValue::OfNat(n) => { let obj = LeanCtor::alloc(3, 1, 0); obj.set(0, Nat::to_lean(n)); - Self::new(*obj) + Self::new(obj.into()) }, DataValue::OfInt(i) => { let obj = LeanCtor::alloc(4, 1, 0); obj.set(0, LeanIxInt::build(i)); - Self::new(*obj) + Self::new(obj.into()) }, DataValue::OfSyntax(syn) => { let obj = LeanCtor::alloc(5, 1, 0); obj.set(0, LeanIxSyntax::build(cache, syn)); - Self::new(*obj) + Self::new(obj.into()) }, } } - /// Build an Array of (Name × DataValue) for mdata. + /// Build an Array of (Name x DataValue) for mdata. pub fn build_kvmap( cache: &mut LeanBuildCache, data: &[(Name, DataValue)], - ) -> LeanArray { + ) -> LeanArray { let arr = LeanArray::alloc(data.len()); for (i, (name, dv)) in data.iter().enumerate() { let name_obj = LeanIxName::build(cache, name); let dv_obj = Self::build(cache, dv); - // Prod (Name × DataValue) + // Prod (Name x DataValue) let pair = LeanCtor::alloc(0, 2, 0); pair.set(0, name_obj); pair.set(1, dv_obj); @@ -332,9 +344,11 @@ impl LeanIxDataValue { } arr } +} +impl LeanIxDataValue { /// Decode Ix.DataValue from a Lean object. - pub fn decode(self) -> DataValue { + pub fn decode(&self) -> DataValue { let ctor = self.as_ctor(); match ctor.tag() { 0 => { @@ -343,22 +357,22 @@ impl LeanIxDataValue { }, 1 => { // ofBool: 0 object fields, 1 scalar byte - let b = ctor.scalar_u8(0, 0) != 0; + let b = ctor.get_u8(0, 0) != 0; DataValue::OfBool(b) }, 2 => { // ofName: 1 object field - DataValue::OfName(LeanIxName::new(ctor.get(0)).decode()) + DataValue::OfName(LeanIxName(ctor.get(0)).decode()) }, 3 => { // ofNat: 1 object field - DataValue::OfNat(Nat::from_obj(ctor.get(0))) + DataValue::OfNat(Nat::from_obj(&ctor.get(0))) }, 4 => { // ofInt: 1 object field let inner = ctor.get(0); let inner_ctor = inner.as_ctor(); - let nat = Nat::from_obj(inner_ctor.get(0)); + let nat = Nat::from_obj(&inner_ctor.get(0)); match inner_ctor.tag() { 0 => DataValue::OfInt(Int::OfNat(nat)), 1 => DataValue::OfInt(Int::NegSucc(nat)), @@ -367,7 +381,7 @@ impl LeanIxDataValue { }, 5 => { // ofSyntax: 1 object field - DataValue::OfSyntax(LeanIxSyntax::new(ctor.get(0)).decode().into()) + DataValue::OfSyntax(LeanIxSyntax(ctor.get(0)).decode().into()) }, _ => panic!("Invalid DataValue tag: {}", ctor.tag()), } @@ -379,55 +393,63 @@ impl LeanIxDataValue { // ============================================================================= /// Round-trip an Ix.Int: decode from Lean, re-encode. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_int(int_ptr: LeanIxInt) -> LeanIxInt { +pub extern "C" fn rs_roundtrip_ix_int( + int_ptr: LeanIxInt>, +) -> LeanIxInt { let int_val = int_ptr.decode(); LeanIxInt::build(&int_val) } /// Round-trip an Ix.Substring: decode from Lean, re-encode. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ix_substring( - sub_ptr: LeanIxSubstring, -) -> LeanIxSubstring { + sub_ptr: LeanIxSubstring>, +) -> LeanIxSubstring { let sub = sub_ptr.decode(); LeanIxSubstring::build(&sub) } /// Round-trip an Ix.SourceInfo: decode from Lean, re-encode. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ix_source_info( - si_ptr: LeanIxSourceInfo, -) -> LeanIxSourceInfo { + si_ptr: LeanIxSourceInfo>, +) -> LeanIxSourceInfo { let si = si_ptr.decode(); LeanIxSourceInfo::build(&si) } /// Round-trip an Ix.SyntaxPreresolved: decode from Lean, re-encode. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ix_syntax_preresolved( - sp_ptr: LeanIxSyntaxPreresolved, -) -> LeanIxSyntaxPreresolved { + sp_ptr: LeanIxSyntaxPreresolved>, +) -> LeanIxSyntaxPreresolved { let sp = sp_ptr.decode(); let mut cache = LeanBuildCache::new(); LeanIxSyntaxPreresolved::build(&mut cache, &sp) } /// Round-trip an Ix.Syntax: decode from Lean, re-encode. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ix_syntax( - syn_ptr: LeanIxSyntax, -) -> LeanIxSyntax { + syn_ptr: LeanIxSyntax>, +) -> LeanIxSyntax { let syn = syn_ptr.decode(); let mut cache = LeanBuildCache::new(); LeanIxSyntax::build(&mut cache, &syn) } /// Round-trip an Ix.DataValue: decode from Lean, re-encode. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ix_data_value( - dv_ptr: LeanIxDataValue, -) -> LeanIxDataValue { + dv_ptr: LeanIxDataValue>, +) -> LeanIxDataValue { let dv = dv_ptr.decode(); let mut cache = LeanBuildCache::new(); LeanIxDataValue::build(&mut cache, &dv) diff --git a/src/ffi/ix/env.rs b/src/ffi/ix/env.rs index 9d4512c5..ceea7709 100644 --- a/src/ffi/ix/env.rs +++ b/src/ffi/ix/env.rs @@ -6,7 +6,7 @@ use crate::ix::env::{ConstantInfo, Name}; use crate::lean::{ LeanIxConstantInfo, LeanIxEnvironment, LeanIxName, LeanIxRawEnvironment, }; -use lean_ffi::object::{LeanArray, LeanCtor, LeanObject}; +use lean_ffi::object::{LeanArray, LeanBorrowed, LeanCtor, LeanOwned, LeanRef}; use crate::ffi::builder::LeanBuildCache; @@ -23,16 +23,16 @@ use crate::ffi::builder::LeanBuildCache; /// /// AssocList α β = nil | cons (key : α) (value : β) (tail : AssocList α β) pub fn build_hashmap_from_pairs( - pairs: Vec<(LeanObject, LeanObject, u64)>, // (key_obj, val_obj, hash) -) -> LeanObject { + pairs: Vec<(LeanOwned, LeanOwned, u64)>, // (key_obj, val_obj, hash) +) -> LeanOwned { let size = pairs.len(); let bucket_count = (size * 4 / 3 + 1).next_power_of_two().max(8); // Create array of AssocLists (initially all nil = boxed 0) let buckets = LeanArray::alloc(bucket_count); - let nil = LeanObject::box_usize(0); + let nil = LeanOwned::box_usize(0); for i in 0..bucket_count { - buckets.set(i, nil); // nil + buckets.set(i, nil.clone()); // nil } // Insert entries @@ -41,7 +41,7 @@ pub fn build_hashmap_from_pairs( usize::try_from(hash).expect("hash overflows usize") % bucket_count; // Get current bucket (AssocList) - let current_tail = buckets.get(bucket_idx); + let current_tail = buckets.get(bucket_idx).to_owned_ref(); // cons (key : α) (value : β) (tail : AssocList α β) -- tag 1 let cons = LeanCtor::alloc(1, 3, 0); @@ -55,12 +55,12 @@ pub fn build_hashmap_from_pairs( // Build Raw { size : Nat, buckets : Array } // Due to unboxing, this IS the HashMap directly // Field 0 = size, Field 1 = buckets (2 object fields, no scalars) - let size_obj = LeanObject::box_usize(size); + let size_obj = LeanOwned::box_usize(size); let raw = LeanCtor::alloc(0, 2, 0); raw.set(0, size_obj); raw.set(1, buckets); - *raw + raw.into() } // ============================================================================= @@ -69,13 +69,13 @@ pub fn build_hashmap_from_pairs( /// Decode a HashMap's AssocList and collect key-value pairs using a custom decoder. fn decode_assoc_list( - obj: LeanObject, + obj: LeanBorrowed<'_>, decode_key: FK, decode_val: FV, ) -> Vec<(K, V)> where - FK: Fn(LeanObject) -> K, - FV: Fn(LeanObject) -> V, + FK: Fn(LeanBorrowed<'_>) -> K, + FV: Fn(LeanBorrowed<'_>) -> V, { let mut result = Vec::new(); let mut current = obj; @@ -93,7 +93,10 @@ where // AssocList.cons: 3 fields (key, value, tail) result.push((decode_key(ctor.get(0)), decode_val(ctor.get(1)))); - current = ctor.get(2); + // Break the borrow chain: ctor borrows from current, so we can't + // reassign current while ctor is alive. The underlying Lean objects + // outlive this loop, so this is safe. + current = unsafe { LeanBorrowed::from_raw(ctor.get(2).as_raw()) }; } result @@ -107,13 +110,13 @@ where /// - DHashMap { inner : Raw, wf : Prop } unboxes to Raw (Prop is erased) /// - Raw { size : Nat, buckets : Array } - field 0 = size, field 1 = buckets fn decode_hashmap( - obj: LeanObject, + obj: LeanBorrowed<'_>, decode_key: FK, decode_val: FV, ) -> Vec<(K, V)> where - FK: Fn(LeanObject) -> K + Copy, - FV: Fn(LeanObject) -> V + Copy, + FK: Fn(LeanBorrowed<'_>) -> K + Copy, + FV: Fn(LeanBorrowed<'_>) -> V + Copy, { let ctor = obj.as_ctor(); // Raw layout: field 0 = size (Nat), field 1 = buckets (Array) @@ -129,7 +132,7 @@ where pairs } -impl LeanIxRawEnvironment { +impl LeanIxRawEnvironment { /// Build a Ix.RawEnvironment from collected caches. /// RawEnvironment has arrays that Lean will convert to HashMaps. /// @@ -155,7 +158,7 @@ impl LeanIxRawEnvironment { consts_arr.set(i, pair); } - Self::new(*consts_arr) + Self::new(consts_arr.into()) } /// Build Ix.RawEnvironment from Vec, preserving order and duplicates. @@ -172,20 +175,23 @@ impl LeanIxRawEnvironment { pair.set(1, val_obj); consts_arr.set(i, pair); } - Self::new(*consts_arr) + Self::new(consts_arr.into()) } +} +impl LeanIxRawEnvironment { /// Decode Ix.RawEnvironment from Lean object into HashMap. /// RawEnvironment = { consts : Array (Name × ConstantInfo) } /// NOTE: Unboxed to just Array. This version deduplicates by name. - pub fn decode(self) -> FxHashMap { - let arr = self.as_array(); + pub fn decode(&self) -> FxHashMap { + let borrowed = unsafe { LeanBorrowed::from_raw(self.as_raw()) }; + let arr = borrowed.as_array(); let mut consts: FxHashMap = FxHashMap::default(); for pair_obj in arr.iter() { let pair = pair_obj.as_ctor(); - let name = LeanIxName::new(pair.get(0)).decode(); - let info = LeanIxConstantInfo::new(pair.get(1)).decode(); + let name = LeanIxName(pair.get(0)).decode(); + let info = LeanIxConstantInfo(pair.get(1)).decode(); consts.insert(name, info); } @@ -194,14 +200,15 @@ impl LeanIxRawEnvironment { /// Decode Ix.RawEnvironment from Lean object preserving array structure. /// This version preserves all entries including duplicates. - pub fn decode_to_vec(self) -> Vec<(Name, ConstantInfo)> { - let arr = self.as_array(); + pub fn decode_to_vec(&self) -> Vec<(Name, ConstantInfo)> { + let borrowed = unsafe { LeanBorrowed::from_raw(self.as_raw()) }; + let arr = borrowed.as_array(); let mut consts = Vec::with_capacity(arr.len()); for pair_obj in arr.iter() { let pair = pair_obj.as_ctor(); - let name = LeanIxName::new(pair.get(0)).decode(); - let info = LeanIxConstantInfo::new(pair.get(1)).decode(); + let name = LeanIxName(pair.get(0)).decode(); + let info = LeanIxConstantInfo(pair.get(1)).decode(); consts.push((name, info)); } @@ -209,7 +216,7 @@ impl LeanIxRawEnvironment { } } -impl LeanIxEnvironment { +impl LeanIxEnvironment { /// Decode Ix.Environment from Lean object. /// /// Ix.Environment = { @@ -218,12 +225,13 @@ impl LeanIxEnvironment { /// /// NOTE: Environment with a single field is UNBOXED by Lean, /// so the pointer IS the HashMap directly, not a structure containing it. - pub fn decode(self) -> FxHashMap { + pub fn decode(&self) -> FxHashMap { // Environment is unboxed - obj IS the HashMap directly + let borrowed = unsafe { LeanBorrowed::from_raw(self.as_raw()) }; let consts_pairs = decode_hashmap( - *self, - |x| LeanIxName::new(x).decode(), - |x| LeanIxConstantInfo::new(x).decode(), + borrowed, + |x| LeanIxName::new(x.to_owned_ref()).decode(), + |x| LeanIxConstantInfo::new(x.to_owned_ref()).decode(), ); let mut consts: FxHashMap = FxHashMap::default(); for (name, info) in consts_pairs { @@ -238,10 +246,11 @@ impl LeanIxEnvironment { // ============================================================================= /// Round-trip an Ix.Environment: decode from Lean, re-encode. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ix_environment( - env_ptr: LeanIxEnvironment, -) -> LeanIxRawEnvironment { + env_ptr: LeanIxEnvironment>, +) -> LeanIxRawEnvironment { let env = env_ptr.decode(); let mut cache = LeanBuildCache::with_capacity(env.len()); LeanIxRawEnvironment::build(&mut cache, &env) @@ -249,10 +258,11 @@ pub extern "C" fn rs_roundtrip_ix_environment( /// Round-trip an Ix.RawEnvironment: decode from Lean, re-encode. /// Uses Vec-preserving functions to maintain array structure and order. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ix_raw_environment( - env_ptr: LeanIxRawEnvironment, -) -> LeanIxRawEnvironment { + env_ptr: LeanIxRawEnvironment>, +) -> LeanIxRawEnvironment { let env = env_ptr.decode_to_vec(); let mut cache = LeanBuildCache::with_capacity(env.len()); LeanIxRawEnvironment::build_from_vec(&mut cache, &env) diff --git a/src/ffi/ix/expr.rs b/src/ffi/ix/expr.rs index 8701ce82..62ea5f16 100644 --- a/src/ffi/ix/expr.rs +++ b/src/ffi/ix/expr.rs @@ -14,71 +14,73 @@ //! - Tag 10: mdata (data : Array (Name × DataValue)) (expr : Expr) (hash : Address) //! - Tag 11: proj (typeName : Name) (idx : Nat) (struct : Expr) (hash : Address) +use crate::ffi::builder::LeanBuildCache; use crate::ix::env::{ BinderInfo, DataValue, Expr, ExprData, Level, Literal, Name, }; +use crate::lean::LeanIxAddress; use crate::lean::{ LeanIxBinderInfo, LeanIxDataValue, LeanIxExpr, LeanIxLevel, LeanIxLiteral, LeanIxName, }; use lean_ffi::nat::Nat; -use lean_ffi::object::{LeanCtor, LeanObject, LeanString}; - -use crate::ffi::builder::LeanBuildCache; -use crate::lean::LeanIxAddress; +use lean_ffi::object::{ + LeanCtor, LeanOwned, LeanRef, LeanString, +}; +#[cfg(feature = "test-ffi")] +use lean_ffi::object::LeanBorrowed; -impl LeanIxExpr { +impl LeanIxExpr { /// Build a Lean Ix.Expr with embedded hash. /// Uses caching to avoid rebuilding the same expression. pub fn build(cache: &mut LeanBuildCache, expr: &Expr) -> Self { let hash = *expr.get_hash(); - if let Some(&cached) = cache.exprs.get(&hash) { - cached.inc_ref(); - return cached; + if let Some(cached) = cache.exprs.get(&hash) { + return cached.clone(); } let result = match expr.as_data() { ExprData::Bvar(idx, h) => { - let obj = LeanCtor::alloc(0, 2, 0); - obj.set(0, Nat::to_lean(idx)); - obj.set(1, LeanIxAddress::build_from_hash(h)); - Self::new(*obj) + let ctor = LeanCtor::alloc(0, 2, 0); + ctor.set(0, Nat::to_lean(idx)); + ctor.set(1, LeanIxAddress::build_from_hash(h)); + Self::new(ctor.into()) }, ExprData::Fvar(name, h) => { - let obj = LeanCtor::alloc(1, 2, 0); - obj.set(0, LeanIxName::build(cache, name)); - obj.set(1, LeanIxAddress::build_from_hash(h)); - Self::new(*obj) + let ctor = LeanCtor::alloc(1, 2, 0); + ctor.set(0, LeanIxName::build(cache, name)); + ctor.set(1, LeanIxAddress::build_from_hash(h)); + Self::new(ctor.into()) }, ExprData::Mvar(name, h) => { - let obj = LeanCtor::alloc(2, 2, 0); - obj.set(0, LeanIxName::build(cache, name)); - obj.set(1, LeanIxAddress::build_from_hash(h)); - Self::new(*obj) + let ctor = LeanCtor::alloc(2, 2, 0); + ctor.set(0, LeanIxName::build(cache, name)); + ctor.set(1, LeanIxAddress::build_from_hash(h)); + Self::new(ctor.into()) }, ExprData::Sort(level, h) => { - let obj = LeanCtor::alloc(3, 2, 0); - obj.set(0, LeanIxLevel::build(cache, level)); - obj.set(1, LeanIxAddress::build_from_hash(h)); - Self::new(*obj) + let ctor = LeanCtor::alloc(3, 2, 0); + ctor.set(0, LeanIxLevel::build(cache, level)); + ctor.set(1, LeanIxAddress::build_from_hash(h)); + Self::new(ctor.into()) }, ExprData::Const(name, levels, h) => { let name_obj = LeanIxName::build(cache, name); let levels_obj = LeanIxLevel::build_array(cache, levels); - let obj = LeanCtor::alloc(4, 3, 0); - obj.set(0, name_obj); - obj.set(1, levels_obj); - obj.set(2, LeanIxAddress::build_from_hash(h)); - Self::new(*obj) + let ctor = LeanCtor::alloc(4, 3, 0); + ctor.set(0, name_obj); + ctor.set(1, levels_obj); + ctor.set(2, LeanIxAddress::build_from_hash(h)); + Self::new(ctor.into()) }, ExprData::App(fn_expr, arg_expr, h) => { let fn_obj = Self::build(cache, fn_expr); let arg_obj = Self::build(cache, arg_expr); - let obj = LeanCtor::alloc(5, 3, 0); - obj.set(0, fn_obj); - obj.set(1, arg_obj); - obj.set(2, LeanIxAddress::build_from_hash(h)); - Self::new(*obj) + let ctor = LeanCtor::alloc(5, 3, 0); + ctor.set(0, fn_obj); + ctor.set(1, arg_obj); + ctor.set(2, LeanIxAddress::build_from_hash(h)); + Self::new(ctor.into()) }, ExprData::Lam(name, ty, body, bi, h) => { let name_obj = LeanIxName::build(cache, name); @@ -86,26 +88,26 @@ impl LeanIxExpr { let body_obj = Self::build(cache, body); let hash_obj = LeanIxAddress::build_from_hash(h); // 4 object fields, 1 scalar byte for BinderInfo - let obj = LeanCtor::alloc(6, 4, 1); - obj.set(0, name_obj); - obj.set(1, ty_obj); - obj.set(2, body_obj); - obj.set(3, hash_obj); - obj.set_scalar_u8(4, 0, LeanIxBinderInfo::to_u8(bi)); - Self::new(*obj) + let ctor = LeanCtor::alloc(6, 4, 1); + ctor.set(0, name_obj); + ctor.set(1, ty_obj); + ctor.set(2, body_obj); + ctor.set(3, hash_obj); + ctor.set_u8(4, 0, LeanIxBinderInfo::::to_u8(bi)); + Self::new(ctor.into()) }, ExprData::ForallE(name, ty, body, bi, h) => { let name_obj = LeanIxName::build(cache, name); let ty_obj = Self::build(cache, ty); let body_obj = Self::build(cache, body); let hash_obj = LeanIxAddress::build_from_hash(h); - let obj = LeanCtor::alloc(7, 4, 1); - obj.set(0, name_obj); - obj.set(1, ty_obj); - obj.set(2, body_obj); - obj.set(3, hash_obj); - obj.set_scalar_u8(4, 0, LeanIxBinderInfo::to_u8(bi)); - Self::new(*obj) + let ctor = LeanCtor::alloc(7, 4, 1); + ctor.set(0, name_obj); + ctor.set(1, ty_obj); + ctor.set(2, body_obj); + ctor.set(3, hash_obj); + ctor.set_u8(4, 0, LeanIxBinderInfo::::to_u8(bi)); + Self::new(ctor.into()) }, ExprData::LetE(name, ty, val, body, non_dep, h) => { let name_obj = LeanIxName::build(cache, name); @@ -114,144 +116,146 @@ impl LeanIxExpr { let body_obj = Self::build(cache, body); let hash_obj = LeanIxAddress::build_from_hash(h); // 5 object fields, 1 scalar byte for Bool - let obj = LeanCtor::alloc(8, 5, 1); - obj.set(0, name_obj); - obj.set(1, ty_obj); - obj.set(2, val_obj); - obj.set(3, body_obj); - obj.set(4, hash_obj); - obj.set_scalar_u8(5, 0, *non_dep as u8); - Self::new(*obj) + let ctor = LeanCtor::alloc(8, 5, 1); + ctor.set(0, name_obj); + ctor.set(1, ty_obj); + ctor.set(2, val_obj); + ctor.set(3, body_obj); + ctor.set(4, hash_obj); + ctor.set_u8(5, 0, *non_dep as u8); + Self::new(ctor.into()) }, ExprData::Lit(lit, h) => { let lit_obj = LeanIxLiteral::build(lit); - let obj = LeanCtor::alloc(9, 2, 0); - obj.set(0, lit_obj); - obj.set(1, LeanIxAddress::build_from_hash(h)); - Self::new(*obj) + let ctor = LeanCtor::alloc(9, 2, 0); + ctor.set(0, lit_obj); + ctor.set(1, LeanIxAddress::build_from_hash(h)); + Self::new(ctor.into()) }, ExprData::Mdata(md, inner, h) => { let md_obj = LeanIxDataValue::build_kvmap(cache, md); let inner_obj = Self::build(cache, inner); - let obj = LeanCtor::alloc(10, 3, 0); - obj.set(0, md_obj); - obj.set(1, inner_obj); - obj.set(2, LeanIxAddress::build_from_hash(h)); - Self::new(*obj) + let ctor = LeanCtor::alloc(10, 3, 0); + ctor.set(0, md_obj); + ctor.set(1, inner_obj); + ctor.set(2, LeanIxAddress::build_from_hash(h)); + Self::new(ctor.into()) }, ExprData::Proj(type_name, idx, struct_expr, h) => { let name_obj = LeanIxName::build(cache, type_name); let idx_obj = Nat::to_lean(idx); let struct_obj = Self::build(cache, struct_expr); - let obj = LeanCtor::alloc(11, 4, 0); - obj.set(0, name_obj); - obj.set(1, idx_obj); - obj.set(2, struct_obj); - obj.set(3, LeanIxAddress::build_from_hash(h)); - Self::new(*obj) + let ctor = LeanCtor::alloc(11, 4, 0); + ctor.set(0, name_obj); + ctor.set(1, idx_obj); + ctor.set(2, struct_obj); + ctor.set(3, LeanIxAddress::build_from_hash(h)); + Self::new(ctor.into()) }, }; - cache.exprs.insert(hash, result); + cache.exprs.insert(hash, result.clone()); result } +} +impl LeanIxExpr { /// Decode a Lean Ix.Expr to Rust Expr. - pub fn decode(self) -> Expr { + pub fn decode(&self) -> Expr { let ctor = self.as_ctor(); match ctor.tag() { 0 => { // bvar - let idx = Nat::from_obj(ctor.get(0)); + let idx = Nat::from_obj(&ctor.get(0)); Expr::bvar(idx) }, 1 => { // fvar - let name = LeanIxName::new(ctor.get(0)).decode(); + let name = LeanIxName(ctor.get(0)).decode(); Expr::fvar(name) }, 2 => { // mvar - let name = LeanIxName::new(ctor.get(0)).decode(); + let name = LeanIxName(ctor.get(0)).decode(); Expr::mvar(name) }, 3 => { // sort - let level = LeanIxLevel::new(ctor.get(0)).decode(); + let level = LeanIxLevel(ctor.get(0)).decode(); Expr::sort(level) }, 4 => { // const - let name = LeanIxName::new(ctor.get(0)).decode(); + let name = LeanIxName(ctor.get(0)).decode(); let levels: Vec = - ctor.get(1).as_array().map(|x| LeanIxLevel::new(x).decode()); + ctor.get(1).as_array().map(|x| LeanIxLevel(x).decode()); Expr::cnst(name, levels) }, 5 => { // app - let fn_expr = Self::new(ctor.get(0)).decode(); - let arg_expr = Self::new(ctor.get(1)).decode(); + let fn_expr = LeanIxExpr(ctor.get(0)).decode(); + let arg_expr = LeanIxExpr(ctor.get(1)).decode(); Expr::app(fn_expr, arg_expr) }, 6 => { // lam: name, ty, body, hash, bi (scalar) - let name = LeanIxName::new(ctor.get(0)).decode(); - let ty = Self::new(ctor.get(1)).decode(); - let body = Self::new(ctor.get(2)).decode(); + let name = LeanIxName(ctor.get(0)).decode(); + let ty = LeanIxExpr(ctor.get(1)).decode(); + let body = LeanIxExpr(ctor.get(2)).decode(); // Read BinderInfo scalar (4 obj fields: name, ty, body, hash) - let bi_byte = ctor.scalar_u8(4, 0); - let bi = LeanIxBinderInfo::from_u8(bi_byte); + let bi_byte = ctor.get_u8(4, 0); + let bi = LeanIxBinderInfo::::from_u8(bi_byte); Expr::lam(name, ty, body, bi) }, 7 => { // forallE: same layout as lam - let name = LeanIxName::new(ctor.get(0)).decode(); - let ty = Self::new(ctor.get(1)).decode(); - let body = Self::new(ctor.get(2)).decode(); + let name = LeanIxName(ctor.get(0)).decode(); + let ty = LeanIxExpr(ctor.get(1)).decode(); + let body = LeanIxExpr(ctor.get(2)).decode(); // 4 obj fields: name, ty, body, hash - let bi_byte = ctor.scalar_u8(4, 0); - let bi = LeanIxBinderInfo::from_u8(bi_byte); + let bi_byte = ctor.get_u8(4, 0); + let bi = LeanIxBinderInfo::::from_u8(bi_byte); Expr::all(name, ty, body, bi) }, 8 => { // letE: name, ty, val, body, hash, nonDep (scalar) - let name = LeanIxName::new(ctor.get(0)).decode(); - let ty = Self::new(ctor.get(1)).decode(); - let val = Self::new(ctor.get(2)).decode(); - let body = Self::new(ctor.get(3)).decode(); + let name = LeanIxName(ctor.get(0)).decode(); + let ty = LeanIxExpr(ctor.get(1)).decode(); + let val = LeanIxExpr(ctor.get(2)).decode(); + let body = LeanIxExpr(ctor.get(3)).decode(); // 5 obj fields: name, ty, val, body, hash - let non_dep = ctor.scalar_u8(5, 0) != 0; + let non_dep = ctor.get_u8(5, 0) != 0; Expr::letE(name, ty, val, body, non_dep) }, 9 => { // lit - let lit = LeanIxLiteral::new(ctor.get(0)).decode(); + let lit = LeanIxLiteral(ctor.get(0)).decode(); Expr::lit(lit) }, 10 => { // mdata: data, expr, hash let data: Vec<(Name, DataValue)> = ctor.get(0).as_array().map(|obj| { let pair = obj.as_ctor(); - let name = LeanIxName::new(pair.get(0)).decode(); - let dv = LeanIxDataValue::new(pair.get(1)).decode(); + let name = LeanIxName(pair.get(0)).decode(); + let dv = LeanIxDataValue(pair.get(1)).decode(); (name, dv) }); - let inner = Self::new(ctor.get(1)).decode(); + let inner = LeanIxExpr(ctor.get(1)).decode(); Expr::mdata(data, inner) }, 11 => { // proj: typeName, idx, struct, hash - let type_name = LeanIxName::new(ctor.get(0)).decode(); - let idx = Nat::from_obj(ctor.get(1)); - let struct_expr = Self::new(ctor.get(2)).decode(); + let type_name = LeanIxName(ctor.get(0)).decode(); + let idx = Nat::from_obj(&ctor.get(1)); + let struct_expr = LeanIxExpr(ctor.get(2)).decode(); Expr::proj(type_name, idx, struct_expr) }, @@ -260,31 +264,32 @@ impl LeanIxExpr { } } -impl LeanIxLiteral { +impl LeanIxLiteral { /// Build a Literal (natVal or strVal). pub fn build(lit: &Literal) -> Self { - let obj = match lit { + match lit { Literal::NatVal(n) => { - let obj = LeanCtor::alloc(0, 1, 0); - obj.set(0, Nat::to_lean(n)); - *obj + let ctor = LeanCtor::alloc(0, 1, 0); + ctor.set(0, Nat::to_lean(n)); + Self::new(ctor.into()) }, Literal::StrVal(s) => { - let obj = LeanCtor::alloc(1, 1, 0); - obj.set(0, LeanString::new(s.as_str())); - *obj + let ctor = LeanCtor::alloc(1, 1, 0); + ctor.set(0, LeanString::new(s.as_str())); + Self::new(ctor.into()) }, - }; - Self::new(obj) + } } +} +impl LeanIxLiteral { /// Decode Lean.Literal from a Lean object. - pub fn decode(self) -> Literal { + pub fn decode(&self) -> Literal { let ctor = self.as_ctor(); match ctor.tag() { 0 => { // natVal - let nat = Nat::from_obj(ctor.get(0)); + let nat = Nat::from_obj(&ctor.get(0)); Literal::NatVal(nat) }, 1 => { @@ -296,11 +301,11 @@ impl LeanIxLiteral { } } -impl LeanIxBinderInfo { +impl LeanIxBinderInfo { /// Build Ix.BinderInfo enum. /// BinderInfo is a 4-constructor enum with no fields, stored as boxed scalar. pub fn build(bi: &BinderInfo) -> Self { - Self::new(LeanObject::box_usize(Self::to_u8(bi) as usize)) + Self::new(LeanOwned::box_usize(Self::to_u8(bi) as usize)) } /// Convert BinderInfo to u8 tag. @@ -326,8 +331,11 @@ impl LeanIxBinderInfo { } /// Round-trip an Ix.Expr: decode from Lean, re-encode via LeanBuildCache. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_expr(expr_ptr: LeanIxExpr) -> LeanIxExpr { +pub extern "C" fn rs_roundtrip_ix_expr( + expr_ptr: LeanIxExpr>, +) -> LeanIxExpr { let expr = expr_ptr.decode(); let mut cache = LeanBuildCache::new(); LeanIxExpr::build(&mut cache, &expr) diff --git a/src/ffi/ix/level.rs b/src/ffi/ix/level.rs index bac3eaf6..47eb5129 100644 --- a/src/ffi/ix/level.rs +++ b/src/ffi/ix/level.rs @@ -10,33 +10,32 @@ use crate::ix::env::{Level, LevelData}; use crate::lean::{LeanIxLevel, LeanIxName}; -use lean_ffi::object::{LeanArray, LeanCtor}; +use lean_ffi::object::{LeanArray, LeanBorrowed, LeanCtor, LeanOwned, LeanRef}; use crate::ffi::builder::LeanBuildCache; use crate::lean::LeanIxAddress; -impl LeanIxLevel { +impl LeanIxLevel { /// Build a Lean Ix.Level with embedded hash. /// Uses caching to avoid rebuilding the same level. pub fn build(cache: &mut LeanBuildCache, level: &Level) -> Self { let hash = *level.get_hash(); - if let Some(&cached) = cache.levels.get(&hash) { - cached.inc_ref(); - return cached; + if let Some(cached) = cache.levels.get(&hash) { + return cached.clone(); } let result = match level.as_data() { LevelData::Zero(h) => { let ctor = LeanCtor::alloc(0, 1, 0); ctor.set(0, LeanIxAddress::build_from_hash(h)); - Self::new(*ctor) + Self::new(ctor.into()) }, LevelData::Succ(x, h) => { let x_obj = Self::build(cache, x); let ctor = LeanCtor::alloc(1, 2, 0); ctor.set(0, x_obj); ctor.set(1, LeanIxAddress::build_from_hash(h)); - Self::new(*ctor) + Self::new(ctor.into()) }, LevelData::Max(x, y, h) => { let x_obj = Self::build(cache, x); @@ -45,7 +44,7 @@ impl LeanIxLevel { ctor.set(0, x_obj); ctor.set(1, y_obj); ctor.set(2, LeanIxAddress::build_from_hash(h)); - Self::new(*ctor) + Self::new(ctor.into()) }, LevelData::Imax(x, y, h) => { let x_obj = Self::build(cache, x); @@ -54,25 +53,25 @@ impl LeanIxLevel { ctor.set(0, x_obj); ctor.set(1, y_obj); ctor.set(2, LeanIxAddress::build_from_hash(h)); - Self::new(*ctor) + Self::new(ctor.into()) }, LevelData::Param(n, h) => { let n_obj = LeanIxName::build(cache, n); let ctor = LeanCtor::alloc(4, 2, 0); ctor.set(0, n_obj); ctor.set(1, LeanIxAddress::build_from_hash(h)); - Self::new(*ctor) + Self::new(ctor.into()) }, LevelData::Mvar(n, h) => { let n_obj = LeanIxName::build(cache, n); let ctor = LeanCtor::alloc(5, 2, 0); ctor.set(0, n_obj); ctor.set(1, LeanIxAddress::build_from_hash(h)); - Self::new(*ctor) + Self::new(ctor.into()) }, }; - cache.levels.insert(hash, result); + cache.levels.insert(hash, result.clone()); result } @@ -80,54 +79,61 @@ impl LeanIxLevel { pub fn build_array( cache: &mut LeanBuildCache, levels: &[Level], - ) -> LeanArray { + ) -> LeanArray { let arr = LeanArray::alloc(levels.len()); for (i, level) in levels.iter().enumerate() { arr.set(i, Self::build(cache, level)); } arr } +} +impl LeanIxLevel { /// Decode a Lean Ix.Level to Rust Level. - pub fn decode(self) -> Level { + pub fn decode(&self) -> Level { let ctor = self.as_ctor(); match ctor.tag() { 0 => Level::zero(), 1 => { - let x = Self::new(ctor.get(0)).decode(); + let x = LeanIxLevel(ctor.get(0)).decode(); Level::succ(x) }, 2 => { - let x = Self::new(ctor.get(0)).decode(); - let y = Self::new(ctor.get(1)).decode(); + let x = LeanIxLevel(ctor.get(0)).decode(); + let y = LeanIxLevel(ctor.get(1)).decode(); Level::max(x, y) }, 3 => { - let x = Self::new(ctor.get(0)).decode(); - let y = Self::new(ctor.get(1)).decode(); + let x = LeanIxLevel(ctor.get(0)).decode(); + let y = LeanIxLevel(ctor.get(1)).decode(); Level::imax(x, y) }, 4 => { - let n = LeanIxName::new(ctor.get(0)).decode(); + let n = LeanIxName(ctor.get(0)).decode(); Level::param(n) }, 5 => { - let n = LeanIxName::new(ctor.get(0)).decode(); + let n = LeanIxName(ctor.get(0)).decode(); Level::mvar(n) }, _ => panic!("Invalid Ix.Level tag: {}", ctor.tag()), } } +} +impl LeanIxLevel> { /// Decode Array of Levels from Lean pointer. - pub fn decode_array(obj: LeanArray) -> Vec { - obj.map(|x| Self::new(x).decode()) + pub fn decode_array(obj: LeanArray>) -> Vec { + obj.map(|x| LeanIxLevel(x).decode()) } } /// Round-trip an Ix.Level: decode from Lean, re-encode via LeanBuildCache. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_level(level_ptr: LeanIxLevel) -> LeanIxLevel { +pub extern "C" fn rs_roundtrip_ix_level( + level_ptr: LeanIxLevel>, +) -> LeanIxLevel { let level = level_ptr.decode(); let mut cache = LeanBuildCache::new(); LeanIxLevel::build(&mut cache, &level) diff --git a/src/ffi/ix/name.rs b/src/ffi/ix/name.rs index a1126019..61ac9c24 100644 --- a/src/ffi/ix/name.rs +++ b/src/ffi/ix/name.rs @@ -8,26 +8,27 @@ use crate::ix::env::{Name, NameData}; use crate::lean::LeanIxName; use lean_ffi::nat::Nat; -use lean_ffi::object::{LeanArray, LeanCtor, LeanString}; +use lean_ffi::object::{ + LeanArray, LeanBorrowed, LeanCtor, LeanOwned, LeanRef, LeanString, +}; use crate::ffi::builder::LeanBuildCache; use crate::lean::LeanIxAddress; -impl LeanIxName { +impl LeanIxName { /// Build a Lean Ix.Name with embedded hash. /// Uses caching to avoid rebuilding the same name. pub fn build(cache: &mut LeanBuildCache, name: &Name) -> Self { let hash = name.get_hash(); - if let Some(&cached) = cache.names.get(hash) { - cached.inc_ref(); - return cached; + if let Some(cached) = cache.names.get(hash) { + return cached.clone(); } let result = match name.as_data() { NameData::Anonymous(h) => { let ctor = LeanCtor::alloc(0, 1, 0); ctor.set(0, LeanIxAddress::build_from_hash(h)); - Self::new(*ctor) + Self::new(ctor.into()) }, NameData::Str(parent, s, h) => { let parent_obj = Self::build(cache, parent); @@ -36,7 +37,7 @@ impl LeanIxName { ctor.set(0, parent_obj); ctor.set(1, s_obj); ctor.set(2, LeanIxAddress::build_from_hash(h)); - Self::new(*ctor) + Self::new(ctor.into()) }, NameData::Num(parent, n, h) => { let parent_obj = Self::build(cache, parent); @@ -45,25 +46,30 @@ impl LeanIxName { ctor.set(0, parent_obj); ctor.set(1, n_obj); ctor.set(2, LeanIxAddress::build_from_hash(h)); - Self::new(*ctor) + Self::new(ctor.into()) }, }; - cache.names.insert(*hash, result); + cache.names.insert(*hash, result.clone()); result } /// Build an Array of Names. - pub fn build_array(cache: &mut LeanBuildCache, names: &[Name]) -> LeanArray { + pub fn build_array( + cache: &mut LeanBuildCache, + names: &[Name], + ) -> LeanArray { let arr = LeanArray::alloc(names.len()); for (i, name) in names.iter().enumerate() { arr.set(i, Self::build(cache, name)); } arr } +} +impl LeanIxName { /// Decode a Lean Ix.Name to Rust Name. - pub fn decode(self) -> Name { + pub fn decode(&self) -> Name { let ctor = self.as_ctor(); match ctor.tag() { 0 => { @@ -72,29 +78,34 @@ impl LeanIxName { }, 1 => { // str: parent, s, hash - let parent = Self::new(ctor.get(0)).decode(); + let parent = LeanIxName(ctor.get(0)).decode(); let s = ctor.get(1).as_string().to_string(); Name::str(parent, s) }, 2 => { // num: parent, i, hash - let parent = Self::new(ctor.get(0)).decode(); - let i = Nat::from_obj(ctor.get(1)); + let parent = LeanIxName(ctor.get(0)).decode(); + let i = Nat::from_obj(&ctor.get(1)); Name::num(parent, i) }, _ => panic!("Invalid Ix.Name tag: {}", ctor.tag()), } } +} +impl LeanIxName> { /// Decode Array of Names from Lean pointer. - pub fn decode_array(obj: LeanArray) -> Vec { - obj.map(|x| Self::new(x).decode()) + pub fn decode_array(obj: LeanArray>) -> Vec { + obj.map(|x| LeanIxName(x).decode()) } } /// Round-trip an Ix.Name: decode from Lean, re-encode via LeanBuildCache. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ix_name(name_ptr: LeanIxName) -> LeanIxName { +pub extern "C" fn rs_roundtrip_ix_name( + name_ptr: LeanIxName>, +) -> LeanIxName { let name = name_ptr.decode(); let mut cache = LeanBuildCache::new(); LeanIxName::build(&mut cache, &name) diff --git a/src/ffi/ixon.rs b/src/ffi/ixon.rs index a2244646..51a89fd0 100644 --- a/src/ffi/ixon.rs +++ b/src/ffi/ixon.rs @@ -3,22 +3,15 @@ //! This module provides build/decode/roundtrip functions for Ixon types used in //! cross-implementation compatibility testing and serialization. +#[cfg(feature = "test-ffi")] pub mod compare; pub mod constant; pub mod enums; pub mod env; pub mod expr; pub mod meta; +#[cfg(feature = "test-ffi")] pub mod serialize; +#[cfg(feature = "test-ffi")] pub mod sharing; pub mod univ; - -pub use compare::*; -pub use constant::*; -pub use enums::*; -pub use env::*; -pub use expr::*; -pub use meta::*; -pub use serialize::*; -pub use sharing::*; -pub use univ::*; diff --git a/src/ffi/ixon/compare.rs b/src/ffi/ixon/compare.rs index 1737cf71..6f613d72 100644 --- a/src/ffi/ixon/compare.rs +++ b/src/ffi/ixon/compare.rs @@ -7,7 +7,9 @@ use crate::ix::env::Name; use crate::ix::ixon::serialize::put_expr; use crate::ix::mutual::MutCtx; use crate::lean::{LeanIxBlockCompareDetail, LeanIxBlockCompareResult}; -use lean_ffi::object::{LeanByteArray, LeanCtor, LeanList, LeanObject}; +use lean_ffi::object::{ + LeanBorrowed, LeanByteArray, LeanCtor, LeanList, LeanOwned, LeanRef, +}; use crate::ffi::lean_env::{ Cache as LeanCache, GlobalCache, decode_expr, decode_name, @@ -21,14 +23,17 @@ pub struct RustBlockEnv { /// Compare Lean's compiled expression output with Rust's compilation of the same input. #[unsafe(no_mangle)] pub extern "C" fn rs_compare_expr_compilation( - lean_expr_ptr: LeanObject, - lean_output: LeanByteArray, + lean_expr_ptr: LeanOwned, + lean_output: LeanByteArray, univ_ctx_size: u64, ) -> bool { // Decode Lean.Expr to Rust's representation let global_cache = GlobalCache::default(); let mut cache = LeanCache::new(&global_cache); - let lean_expr = decode_expr(lean_expr_ptr, &mut cache); + let lean_expr = decode_expr( + unsafe { LeanBorrowed::from_raw(lean_expr_ptr.as_raw()) }, + &mut cache, + ); // Create universe params for de Bruijn indexing (u0, u1, u2, ...) let univ_params: Vec = (0..univ_ctx_size) @@ -61,7 +66,7 @@ pub extern "C" fn rs_compare_expr_compilation( rust_bytes == lean_bytes } -impl LeanIxBlockCompareResult { +impl LeanIxBlockCompareResult { /// Build a BlockCompareResult Lean object. fn build( matched: bool, @@ -71,33 +76,33 @@ impl LeanIxBlockCompareResult { first_diff_offset: u64, ) -> Self { let obj = if matched { - *LeanCtor::alloc(0, 0, 0) // match + LeanCtor::alloc(0, 0, 0).into() // match } else if not_found { - *LeanCtor::alloc(2, 0, 0) // notFound + LeanCtor::alloc(2, 0, 0).into() // notFound } else { // mismatch let ctor = LeanCtor::alloc(1, 0, 24); - ctor.set_scalar_u64(0, 0, lean_size); - ctor.set_scalar_u64(0, 8, rust_size); - ctor.set_scalar_u64(0, 16, first_diff_offset); - *ctor + ctor.set_u64(0, 0, lean_size); + ctor.set_u64(0, 8, rust_size); + ctor.set_u64(0, 16, first_diff_offset); + ctor.into() }; Self::new(obj) } } -impl LeanIxBlockCompareDetail { +impl LeanIxBlockCompareDetail { /// Build a BlockCompareDetail Lean object. fn build( - result: LeanIxBlockCompareResult, + result: LeanIxBlockCompareResult, lean_sharing_len: u64, rust_sharing_len: u64, ) -> Self { let ctor = LeanCtor::alloc(0, 1, 16); ctor.set(0, result); - ctor.set_scalar_u64(1, 0, lean_sharing_len); - ctor.set_scalar_u64(1, 8, rust_sharing_len); - Self::new(*ctor) + ctor.set_u64(1, 0, lean_sharing_len); + ctor.set_u64(1, 8, rust_sharing_len); + Self::new(ctor.into()) } } @@ -109,12 +114,15 @@ impl LeanIxBlockCompareDetail { #[unsafe(no_mangle)] pub unsafe extern "C" fn rs_compare_block_v2( rust_env: *const RustBlockEnv, - lowlink_name: LeanObject, - lean_bytes: LeanByteArray, + lowlink_name: LeanOwned, + lean_bytes: LeanByteArray, lean_sharing_len: u64, -) -> LeanIxBlockCompareDetail { +) -> LeanIxBlockCompareDetail { let global_cache = GlobalCache::default(); - let name = decode_name(lowlink_name, &global_cache); + let name = decode_name( + unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + &global_cache, + ); let rust_env = unsafe { &*rust_env }; let lean_data = lean_bytes.as_bytes(); @@ -192,7 +200,7 @@ pub unsafe extern "C" fn rs_free_compiled_env(ptr: *mut RustBlockEnv) { /// Build a RustBlockEnv from a Lean environment. #[unsafe(no_mangle)] pub extern "C" fn rs_build_compiled_env( - env_consts_ptr: LeanList, + env_consts_ptr: LeanList>, ) -> *mut RustBlockEnv { use crate::ffi::lean_env::decode_env; diff --git a/src/ffi/ixon/constant.rs b/src/ffi/ixon/constant.rs index 59e70ef6..140ba968 100644 --- a/src/ffi/ixon/constant.rs +++ b/src/ffi/ixon/constant.rs @@ -21,13 +21,15 @@ use crate::lean::{ LeanIxonInductiveProj, LeanIxonMutConst, LeanIxonQuotient, LeanIxonRecursor, LeanIxonRecursorProj, LeanIxonRecursorRule, LeanIxonUniv, }; -use lean_ffi::object::{LeanArray, LeanCtor}; +use lean_ffi::object::{LeanArray, LeanCtor, LeanOwned, LeanRef}; +#[cfg(feature = "test-ffi")] +use lean_ffi::object::LeanBorrowed; // ============================================================================= // Definition // ============================================================================= -impl LeanIxonDefinition { +impl LeanIxonDefinition { /// Build Ixon.Definition /// Lean stores scalar fields ordered by size (largest first). /// Layout: header(8) + typ(8) + value(8) + lvls(8) + kind(1) + safety(1) + padding(6) @@ -39,38 +41,41 @@ impl LeanIxonDefinition { ctor.set(0, typ_obj); ctor.set(1, value_obj); // Scalar offsets from obj_cptr: 2*8=16 for lvls, 2*8+8=24 for kind, 2*8+9=25 for safety - ctor.set_scalar_u64(2, 0, def.lvls); + ctor.set_u64(2, 0, def.lvls); let kind_val: u8 = match def.kind { DefKind::Definition => 0, DefKind::Opaque => 1, DefKind::Theorem => 2, }; - ctor.set_scalar_u8(2, 8, kind_val); + ctor.set_u8(2, 8, kind_val); let safety_val: u8 = match def.safety { crate::ix::env::DefinitionSafety::Unsafe => 0, crate::ix::env::DefinitionSafety::Safe => 1, crate::ix::env::DefinitionSafety::Partial => 2, }; - ctor.set_scalar_u8(2, 9, safety_val); - Self::new(*ctor) + ctor.set_u8(2, 9, safety_val); + Self::new(ctor.into()) } +} +impl LeanIxonDefinition { /// Decode Ixon.Definition. /// Lean stores scalar fields ordered by size (largest first). /// Layout: header(8) + typ(8) + value(8) + lvls(8) + kind(1) + safety(1) + padding(6) - pub fn decode(self) -> IxonDefinition { + pub fn decode(&self) -> IxonDefinition { let ctor = self.as_ctor(); - let typ = Arc::new(LeanIxonExpr::new(ctor.get(0)).decode()); - let value = Arc::new(LeanIxonExpr::new(ctor.get(1)).decode()); - let lvls = ctor.scalar_u64(2, 0); - let kind_val = ctor.scalar_u8(2, 8); + let typ = Arc::new(LeanIxonExpr::new(ctor.get(0).to_owned_ref()).decode()); + let value = + Arc::new(LeanIxonExpr::new(ctor.get(1).to_owned_ref()).decode()); + let lvls = ctor.get_u64(2, 0); + let kind_val = ctor.get_u8(2, 8); let kind = match kind_val { 0 => DefKind::Definition, 1 => DefKind::Opaque, 2 => DefKind::Theorem, _ => panic!("Invalid DefKind: {}", kind_val), }; - let safety_val = ctor.scalar_u8(2, 9); + let safety_val = ctor.get_u8(2, 9); let safety = match safety_val { 0 => crate::ix::env::DefinitionSafety::Unsafe, 1 => crate::ix::env::DefinitionSafety::Safe, @@ -85,22 +90,24 @@ impl LeanIxonDefinition { // RecursorRule // ============================================================================= -impl LeanIxonRecursorRule { +impl LeanIxonRecursorRule { /// Build Ixon.RecursorRule pub fn build(rule: &IxonRecursorRule) -> Self { let rhs_obj = LeanIxonExpr::build(&rule.rhs); // 1 obj field, 8 scalar bytes let ctor = LeanCtor::alloc(0, 1, 8); ctor.set(0, rhs_obj); - ctor.set_scalar_u64(1, 0, rule.fields); - Self::new(*ctor) + ctor.set_u64(1, 0, rule.fields); + Self::new(ctor.into()) } +} +impl LeanIxonRecursorRule { /// Decode Ixon.RecursorRule. - pub fn decode(self) -> IxonRecursorRule { + pub fn decode(&self) -> IxonRecursorRule { let ctor = self.as_ctor(); - let rhs = Arc::new(LeanIxonExpr::new(ctor.get(0)).decode()); - let fields = ctor.scalar_u64(1, 0); + let rhs = Arc::new(LeanIxonExpr::new(ctor.get(0).to_owned_ref()).decode()); + let fields = ctor.get_u64(1, 0); IxonRecursorRule { fields, rhs } } } @@ -109,7 +116,7 @@ impl LeanIxonRecursorRule { // Recursor // ============================================================================= -impl LeanIxonRecursor { +impl LeanIxonRecursor { /// Build Ixon.Recursor /// Scalars ordered by size: lvls(8) + params(8) + indices(8) + motives(8) + minors(8) + k(1) + isUnsafe(1) + padding(6) pub fn build(rec: &IxonRecursor) -> Self { @@ -124,30 +131,33 @@ impl LeanIxonRecursor { ctor.set(0, typ_obj); ctor.set(1, rules_arr); // Scalar offsets from obj_cptr: 2*8=16 base - ctor.set_scalar_u64(2, 0, rec.lvls); - ctor.set_scalar_u64(2, 8, rec.params); - ctor.set_scalar_u64(2, 16, rec.indices); - ctor.set_scalar_u64(2, 24, rec.motives); - ctor.set_scalar_u64(2, 32, rec.minors); - ctor.set_scalar_u8(2, 40, if rec.k { 1 } else { 0 }); - ctor.set_scalar_u8(2, 41, if rec.is_unsafe { 1 } else { 0 }); - Self::new(*ctor) + ctor.set_u64(2, 0, rec.lvls); + ctor.set_u64(2, 8, rec.params); + ctor.set_u64(2, 16, rec.indices); + ctor.set_u64(2, 24, rec.motives); + ctor.set_u64(2, 32, rec.minors); + ctor.set_u8(2, 40, if rec.k { 1 } else { 0 }); + ctor.set_u8(2, 41, if rec.is_unsafe { 1 } else { 0 }); + Self::new(ctor.into()) } +} +impl LeanIxonRecursor { /// Decode Ixon.Recursor. /// Scalars ordered by size: lvls(8) + params(8) + indices(8) + motives(8) + minors(8) + k(1) + isUnsafe(1) + padding(6) - pub fn decode(self) -> IxonRecursor { + pub fn decode(&self) -> IxonRecursor { let ctor = self.as_ctor(); - let typ = Arc::new(LeanIxonExpr::new(ctor.get(0)).decode()); + let typ = Arc::new(LeanIxonExpr::new(ctor.get(0).to_owned_ref()).decode()); let rules_arr = ctor.get(1).as_array(); - let rules = rules_arr.map(|x| LeanIxonRecursorRule::new(x).decode()); - let lvls = ctor.scalar_u64(2, 0); - let params = ctor.scalar_u64(2, 8); - let indices = ctor.scalar_u64(2, 16); - let motives = ctor.scalar_u64(2, 24); - let minors = ctor.scalar_u64(2, 32); - let k = ctor.scalar_u8(2, 40) != 0; - let is_unsafe = ctor.scalar_u8(2, 41) != 0; + let rules = + rules_arr.map(|x| LeanIxonRecursorRule::new(x.to_owned_ref()).decode()); + let lvls = ctor.get_u64(2, 0); + let params = ctor.get_u64(2, 8); + let indices = ctor.get_u64(2, 16); + let motives = ctor.get_u64(2, 24); + let minors = ctor.get_u64(2, 32); + let k = ctor.get_u8(2, 40) != 0; + let is_unsafe = ctor.get_u8(2, 41) != 0; IxonRecursor { k, is_unsafe, @@ -166,7 +176,7 @@ impl LeanIxonRecursor { // Axiom // ============================================================================= -impl LeanIxonAxiom { +impl LeanIxonAxiom { /// Build Ixon.Axiom /// Scalars ordered by size: lvls(8) + isUnsafe(1) + padding(7) pub fn build(ax: &IxonAxiom) -> Self { @@ -175,18 +185,20 @@ impl LeanIxonAxiom { let ctor = LeanCtor::alloc(0, 1, 16); ctor.set(0, typ_obj); // Scalar offsets from obj_cptr: 1*8=8 base - ctor.set_scalar_u64(1, 0, ax.lvls); - ctor.set_scalar_u8(1, 8, if ax.is_unsafe { 1 } else { 0 }); - Self::new(*ctor) + ctor.set_u64(1, 0, ax.lvls); + ctor.set_u8(1, 8, if ax.is_unsafe { 1 } else { 0 }); + Self::new(ctor.into()) } +} +impl LeanIxonAxiom { /// Decode Ixon.Axiom. /// Scalars ordered by size: lvls(8) + isUnsafe(1) + padding(7) - pub fn decode(self) -> IxonAxiom { + pub fn decode(&self) -> IxonAxiom { let ctor = self.as_ctor(); - let typ = Arc::new(LeanIxonExpr::new(ctor.get(0)).decode()); - let lvls = ctor.scalar_u64(1, 0); - let is_unsafe = ctor.scalar_u8(1, 8) != 0; + let typ = Arc::new(LeanIxonExpr::new(ctor.get(0).to_owned_ref()).decode()); + let lvls = ctor.get_u64(1, 0); + let is_unsafe = ctor.get_u8(1, 8) != 0; IxonAxiom { is_unsafe, lvls, typ } } } @@ -195,7 +207,7 @@ impl LeanIxonAxiom { // Quotient // ============================================================================= -impl LeanIxonQuotient { +impl LeanIxonQuotient { /// Build Ixon.Quotient /// QuotKind is a simple enum stored as scalar u8, not object field. /// Scalars ordered by size: lvls(8) + kind(1) + padding(7) @@ -205,24 +217,26 @@ impl LeanIxonQuotient { let ctor = LeanCtor::alloc(0, 1, 16); ctor.set(0, typ_obj); // Scalar offsets from obj_cptr: 1*8=8 base - ctor.set_scalar_u64(1, 0, quot.lvls); + ctor.set_u64(1, 0, quot.lvls); let kind_val: u8 = match quot.kind { crate::ix::env::QuotKind::Type => 0, crate::ix::env::QuotKind::Ctor => 1, crate::ix::env::QuotKind::Lift => 2, crate::ix::env::QuotKind::Ind => 3, }; - ctor.set_scalar_u8(1, 8, kind_val); - Self::new(*ctor) + ctor.set_u8(1, 8, kind_val); + Self::new(ctor.into()) } +} +impl LeanIxonQuotient { /// Decode Ixon.Quotient. /// QuotKind is a scalar (not object field). Scalars: lvls(8) + kind(1) + padding(7) - pub fn decode(self) -> IxonQuotient { + pub fn decode(&self) -> IxonQuotient { let ctor = self.as_ctor(); - let typ = Arc::new(LeanIxonExpr::new(ctor.get(0)).decode()); - let lvls = ctor.scalar_u64(1, 0); - let kind_val = ctor.scalar_u8(1, 8); + let typ = Arc::new(LeanIxonExpr::new(ctor.get(0).to_owned_ref()).decode()); + let lvls = ctor.get_u64(1, 0); + let kind_val = ctor.get_u8(1, 8); let kind = match kind_val { 0 => crate::ix::env::QuotKind::Type, 1 => crate::ix::env::QuotKind::Ctor, @@ -238,7 +252,7 @@ impl LeanIxonQuotient { // Constructor // ============================================================================= -impl LeanIxonConstructor { +impl LeanIxonConstructor { /// Build Ixon.Constructor /// Scalars ordered by size: lvls(8) + cidx(8) + params(8) + fields(8) + isUnsafe(1) + padding(7) pub fn build(c: &IxonConstructor) -> Self { @@ -247,24 +261,26 @@ impl LeanIxonConstructor { let ctor = LeanCtor::alloc(0, 1, 40); ctor.set(0, typ_obj); // Scalar offsets from obj_cptr: 1*8=8 base - ctor.set_scalar_u64(1, 0, c.lvls); - ctor.set_scalar_u64(1, 8, c.cidx); - ctor.set_scalar_u64(1, 16, c.params); - ctor.set_scalar_u64(1, 24, c.fields); - ctor.set_scalar_u8(1, 32, if c.is_unsafe { 1 } else { 0 }); - Self::new(*ctor) + ctor.set_u64(1, 0, c.lvls); + ctor.set_u64(1, 8, c.cidx); + ctor.set_u64(1, 16, c.params); + ctor.set_u64(1, 24, c.fields); + ctor.set_u8(1, 32, if c.is_unsafe { 1 } else { 0 }); + Self::new(ctor.into()) } +} +impl LeanIxonConstructor { /// Decode Ixon.Constructor. /// Scalars ordered by size: lvls(8) + cidx(8) + params(8) + fields(8) + isUnsafe(1) + padding(7) - pub fn decode(self) -> IxonConstructor { + pub fn decode(&self) -> IxonConstructor { let ctor = self.as_ctor(); - let typ = Arc::new(LeanIxonExpr::new(ctor.get(0)).decode()); - let lvls = ctor.scalar_u64(1, 0); - let cidx = ctor.scalar_u64(1, 8); - let params = ctor.scalar_u64(1, 16); - let fields = ctor.scalar_u64(1, 24); - let is_unsafe = ctor.scalar_u8(1, 32) != 0; + let typ = Arc::new(LeanIxonExpr::new(ctor.get(0).to_owned_ref()).decode()); + let lvls = ctor.get_u64(1, 0); + let cidx = ctor.get_u64(1, 8); + let params = ctor.get_u64(1, 16); + let fields = ctor.get_u64(1, 24); + let is_unsafe = ctor.get_u8(1, 32) != 0; IxonConstructor { is_unsafe, lvls, cidx, params, fields, typ } } } @@ -273,7 +289,7 @@ impl LeanIxonConstructor { // Inductive // ============================================================================= -impl LeanIxonInductive { +impl LeanIxonInductive { /// Build Ixon.Inductive /// Scalars ordered by size: lvls(8) + params(8) + indices(8) + nested(8) + recr(1) + refl(1) + isUnsafe(1) + padding(5) pub fn build(ind: &IxonInductive) -> Self { @@ -288,30 +304,33 @@ impl LeanIxonInductive { ctor.set(0, typ_obj); ctor.set(1, ctors_arr); // Scalar offsets from obj_cptr: 2*8=16 base - ctor.set_scalar_u64(2, 0, ind.lvls); - ctor.set_scalar_u64(2, 8, ind.params); - ctor.set_scalar_u64(2, 16, ind.indices); - ctor.set_scalar_u64(2, 24, ind.nested); - ctor.set_scalar_u8(2, 32, if ind.recr { 1 } else { 0 }); - ctor.set_scalar_u8(2, 33, if ind.refl { 1 } else { 0 }); - ctor.set_scalar_u8(2, 34, if ind.is_unsafe { 1 } else { 0 }); - Self::new(*ctor) + ctor.set_u64(2, 0, ind.lvls); + ctor.set_u64(2, 8, ind.params); + ctor.set_u64(2, 16, ind.indices); + ctor.set_u64(2, 24, ind.nested); + ctor.set_u8(2, 32, if ind.recr { 1 } else { 0 }); + ctor.set_u8(2, 33, if ind.refl { 1 } else { 0 }); + ctor.set_u8(2, 34, if ind.is_unsafe { 1 } else { 0 }); + Self::new(ctor.into()) } +} +impl LeanIxonInductive { /// Decode Ixon.Inductive. /// Scalars ordered by size: lvls(8) + params(8) + indices(8) + nested(8) + recr(1) + refl(1) + isUnsafe(1) + padding(5) - pub fn decode(self) -> IxonInductive { + pub fn decode(&self) -> IxonInductive { let ctor = self.as_ctor(); - let typ = Arc::new(LeanIxonExpr::new(ctor.get(0)).decode()); + let typ = Arc::new(LeanIxonExpr::new(ctor.get(0).to_owned_ref()).decode()); let ctors_arr = ctor.get(1).as_array(); - let ctors = ctors_arr.map(|x| LeanIxonConstructor::new(x).decode()); - let lvls = ctor.scalar_u64(2, 0); - let params = ctor.scalar_u64(2, 8); - let indices = ctor.scalar_u64(2, 16); - let nested = ctor.scalar_u64(2, 24); - let recr = ctor.scalar_u8(2, 32) != 0; - let refl = ctor.scalar_u8(2, 33) != 0; - let is_unsafe = ctor.scalar_u8(2, 34) != 0; + let ctors = + ctors_arr.map(|x| LeanIxonConstructor::new(x.to_owned_ref()).decode()); + let lvls = ctor.get_u64(2, 0); + let params = ctor.get_u64(2, 8); + let indices = ctor.get_u64(2, 16); + let nested = ctor.get_u64(2, 24); + let recr = ctor.get_u8(2, 32) != 0; + let refl = ctor.get_u8(2, 33) != 0; + let is_unsafe = ctor.get_u8(2, 34) != 0; IxonInductive { recr, refl, @@ -330,72 +349,84 @@ impl LeanIxonInductive { // Projection Types // ============================================================================= -impl LeanIxonInductiveProj { +impl LeanIxonInductiveProj { pub fn build(proj: &InductiveProj) -> Self { let block_obj = LeanIxAddress::build(&proj.block); let ctor = LeanCtor::alloc(0, 1, 8); ctor.set(0, block_obj); - ctor.set_scalar_u64(1, 0, proj.idx); - Self::new(*ctor) + ctor.set_u64(1, 0, proj.idx); + Self::new(ctor.into()) } +} - pub fn decode(self) -> InductiveProj { +impl LeanIxonInductiveProj { + pub fn decode(&self) -> InductiveProj { let ctor = self.as_ctor(); - let block = LeanIxAddress::new(ctor.get(0)).decode(); - let idx = ctor.scalar_u64(1, 0); + let block = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); + let idx = ctor.get_u64(1, 0); InductiveProj { idx, block } } } -impl LeanIxonConstructorProj { +impl LeanIxonConstructorProj { pub fn build(proj: &ConstructorProj) -> Self { let block_obj = LeanIxAddress::build(&proj.block); let ctor = LeanCtor::alloc(0, 1, 16); ctor.set(0, block_obj); - ctor.set_scalar_u64(1, 0, proj.idx); - ctor.set_scalar_u64(1, 8, proj.cidx); - Self::new(*ctor) + ctor.set_u64(1, 0, proj.idx); + ctor.set_u64(1, 8, proj.cidx); + Self::new(ctor.into()) } +} - pub fn decode(self) -> ConstructorProj { +impl LeanIxonConstructorProj { + pub fn decode(&self) -> ConstructorProj { let ctor = self.as_ctor(); - let block = LeanIxAddress::new(ctor.get(0)).decode(); - let idx = ctor.scalar_u64(1, 0); - let cidx = ctor.scalar_u64(1, 8); + let block = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); + let idx = ctor.get_u64(1, 0); + let cidx = ctor.get_u64(1, 8); ConstructorProj { idx, cidx, block } } } -impl LeanIxonRecursorProj { +impl LeanIxonRecursorProj { pub fn build(proj: &RecursorProj) -> Self { let block_obj = LeanIxAddress::build(&proj.block); let ctor = LeanCtor::alloc(0, 1, 8); ctor.set(0, block_obj); - ctor.set_scalar_u64(1, 0, proj.idx); - Self::new(*ctor) + ctor.set_u64(1, 0, proj.idx); + Self::new(ctor.into()) } +} - pub fn decode(self) -> RecursorProj { +impl LeanIxonRecursorProj { + pub fn decode(&self) -> RecursorProj { let ctor = self.as_ctor(); - let block = LeanIxAddress::new(ctor.get(0)).decode(); - let idx = ctor.scalar_u64(1, 0); + let block = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); + let idx = ctor.get_u64(1, 0); RecursorProj { idx, block } } } -impl LeanIxonDefinitionProj { +impl LeanIxonDefinitionProj { pub fn build(proj: &DefinitionProj) -> Self { let block_obj = LeanIxAddress::build(&proj.block); let ctor = LeanCtor::alloc(0, 1, 8); ctor.set(0, block_obj); - ctor.set_scalar_u64(1, 0, proj.idx); - Self::new(*ctor) + ctor.set_u64(1, 0, proj.idx); + Self::new(ctor.into()) } +} - pub fn decode(self) -> DefinitionProj { +impl LeanIxonDefinitionProj { + pub fn decode(&self) -> DefinitionProj { let ctor = self.as_ctor(); - let block = LeanIxAddress::new(ctor.get(0)).decode(); - let idx = ctor.scalar_u64(1, 0); + let block = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); + let idx = ctor.get_u64(1, 0); DefinitionProj { idx, block } } } @@ -404,38 +435,44 @@ impl LeanIxonDefinitionProj { // MutConst // ============================================================================= -impl LeanIxonMutConst { +impl LeanIxonMutConst { pub fn build(mc: &MutConst) -> Self { let obj = match mc { MutConst::Defn(def) => { let def_obj = LeanIxonDefinition::build(def); let ctor = LeanCtor::alloc(0, 1, 0); ctor.set(0, def_obj); - *ctor + ctor.into() }, MutConst::Indc(ind) => { let ind_obj = LeanIxonInductive::build(ind); let ctor = LeanCtor::alloc(1, 1, 0); ctor.set(0, ind_obj); - *ctor + ctor.into() }, MutConst::Recr(rec) => { let rec_obj = LeanIxonRecursor::build(rec); let ctor = LeanCtor::alloc(2, 1, 0); ctor.set(0, rec_obj); - *ctor + ctor.into() }, }; Self::new(obj) } +} - pub fn decode(self) -> MutConst { +impl LeanIxonMutConst { + pub fn decode(&self) -> MutConst { let ctor = self.as_ctor(); let inner = ctor.get(0); match ctor.tag() { - 0 => MutConst::Defn(LeanIxonDefinition::new(inner).decode()), - 1 => MutConst::Indc(LeanIxonInductive::new(inner).decode()), - 2 => MutConst::Recr(LeanIxonRecursor::new(inner).decode()), + 0 => { + MutConst::Defn(LeanIxonDefinition::new(inner.to_owned_ref()).decode()) + }, + 1 => { + MutConst::Indc(LeanIxonInductive::new(inner.to_owned_ref()).decode()) + }, + 2 => MutConst::Recr(LeanIxonRecursor::new(inner.to_owned_ref()).decode()), tag => panic!("Invalid Ixon.MutConst tag: {}", tag), } } @@ -445,7 +482,7 @@ impl LeanIxonMutConst { // ConstantInfo // ============================================================================= -impl LeanIxonConstantInfo { +impl LeanIxonConstantInfo { /// Build Ixon.ConstantInfo (9 constructors) pub fn build(info: &IxonConstantInfo) -> Self { let obj = match info { @@ -453,49 +490,49 @@ impl LeanIxonConstantInfo { let def_obj = LeanIxonDefinition::build(def); let ctor = LeanCtor::alloc(0, 1, 0); ctor.set(0, def_obj); - *ctor + ctor.into() }, IxonConstantInfo::Recr(rec) => { let rec_obj = LeanIxonRecursor::build(rec); let ctor = LeanCtor::alloc(1, 1, 0); ctor.set(0, rec_obj); - *ctor + ctor.into() }, IxonConstantInfo::Axio(ax) => { let ax_obj = LeanIxonAxiom::build(ax); let ctor = LeanCtor::alloc(2, 1, 0); ctor.set(0, ax_obj); - *ctor + ctor.into() }, IxonConstantInfo::Quot(quot) => { let quot_obj = LeanIxonQuotient::build(quot); let ctor = LeanCtor::alloc(3, 1, 0); ctor.set(0, quot_obj); - *ctor + ctor.into() }, IxonConstantInfo::CPrj(proj) => { let proj_obj = LeanIxonConstructorProj::build(proj); let ctor = LeanCtor::alloc(4, 1, 0); ctor.set(0, proj_obj); - *ctor + ctor.into() }, IxonConstantInfo::RPrj(proj) => { let proj_obj = LeanIxonRecursorProj::build(proj); let ctor = LeanCtor::alloc(5, 1, 0); ctor.set(0, proj_obj); - *ctor + ctor.into() }, IxonConstantInfo::IPrj(proj) => { let proj_obj = LeanIxonInductiveProj::build(proj); let ctor = LeanCtor::alloc(6, 1, 0); ctor.set(0, proj_obj); - *ctor + ctor.into() }, IxonConstantInfo::DPrj(proj) => { let proj_obj = LeanIxonDefinitionProj::build(proj); let ctor = LeanCtor::alloc(7, 1, 0); ctor.set(0, proj_obj); - *ctor + ctor.into() }, IxonConstantInfo::Muts(muts) => { let arr = LeanArray::alloc(muts.len()); @@ -504,28 +541,47 @@ impl LeanIxonConstantInfo { } let ctor = LeanCtor::alloc(8, 1, 0); ctor.set(0, arr); - *ctor + ctor.into() }, }; Self::new(obj) } +} +impl LeanIxonConstantInfo { /// Decode Ixon.ConstantInfo. - pub fn decode(self) -> IxonConstantInfo { + pub fn decode(&self) -> IxonConstantInfo { let ctor = self.as_ctor(); let inner = ctor.get(0); match ctor.tag() { - 0 => IxonConstantInfo::Defn(LeanIxonDefinition::new(inner).decode()), - 1 => IxonConstantInfo::Recr(LeanIxonRecursor::new(inner).decode()), - 2 => IxonConstantInfo::Axio(LeanIxonAxiom::new(inner).decode()), - 3 => IxonConstantInfo::Quot(LeanIxonQuotient::new(inner).decode()), - 4 => IxonConstantInfo::CPrj(LeanIxonConstructorProj::new(inner).decode()), - 5 => IxonConstantInfo::RPrj(LeanIxonRecursorProj::new(inner).decode()), - 6 => IxonConstantInfo::IPrj(LeanIxonInductiveProj::new(inner).decode()), - 7 => IxonConstantInfo::DPrj(LeanIxonDefinitionProj::new(inner).decode()), + 0 => IxonConstantInfo::Defn( + LeanIxonDefinition::new(inner.to_owned_ref()).decode(), + ), + 1 => IxonConstantInfo::Recr( + LeanIxonRecursor::new(inner.to_owned_ref()).decode(), + ), + 2 => IxonConstantInfo::Axio( + LeanIxonAxiom::new(inner.to_owned_ref()).decode(), + ), + 3 => IxonConstantInfo::Quot( + LeanIxonQuotient::new(inner.to_owned_ref()).decode(), + ), + 4 => IxonConstantInfo::CPrj( + LeanIxonConstructorProj::new(inner.to_owned_ref()).decode(), + ), + 5 => IxonConstantInfo::RPrj( + LeanIxonRecursorProj::new(inner.to_owned_ref()).decode(), + ), + 6 => IxonConstantInfo::IPrj( + LeanIxonInductiveProj::new(inner.to_owned_ref()).decode(), + ), + 7 => IxonConstantInfo::DPrj( + LeanIxonDefinitionProj::new(inner.to_owned_ref()).decode(), + ), 8 => { let arr = inner.as_array(); - let muts = arr.map(|x| LeanIxonMutConst::new(x).decode()); + let muts = + arr.map(|x| LeanIxonMutConst::new(x.to_owned_ref()).decode()); IxonConstantInfo::Muts(muts) }, tag => panic!("Invalid Ixon.ConstantInfo tag: {}", tag), @@ -537,7 +593,7 @@ impl LeanIxonConstantInfo { // Constant // ============================================================================= -impl LeanIxonConstant { +impl LeanIxonConstant { /// Build Ixon.Constant pub fn build(constant: &IxonConstant) -> Self { let info_obj = LeanIxonConstantInfo::build(&constant.info); @@ -549,17 +605,19 @@ impl LeanIxonConstant { ctor.set(1, sharing_obj); ctor.set(2, refs_obj); ctor.set(3, univs_obj); - Self::new(*ctor) + Self::new(ctor.into()) } +} +impl LeanIxonConstant { /// Decode Ixon.Constant. - pub fn decode(self) -> IxonConstant { + pub fn decode(&self) -> IxonConstant { let ctor = self.as_ctor(); IxonConstant { - info: LeanIxonConstantInfo::new(ctor.get(0)).decode(), - sharing: LeanIxonExpr::decode_array(ctor.get(1).as_array()), + info: LeanIxonConstantInfo::new(ctor.get(0).to_owned_ref()).decode(), + sharing: LeanIxonExpr::decode_array(&ctor.get(1).as_array()), refs: LeanIxAddress::decode_array(ctor.get(2).as_array()), - univs: LeanIxonUniv::decode_array(ctor.get(3).as_array()), + univs: LeanIxonUniv::decode_array(&ctor.get(3).as_array()), } } } @@ -569,125 +627,141 @@ impl LeanIxonConstant { // ============================================================================= /// Round-trip Ixon.Definition. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_definition( - obj: LeanIxonDefinition, -) -> LeanIxonDefinition { + obj: LeanIxonDefinition>, +) -> LeanIxonDefinition { let def = obj.decode(); LeanIxonDefinition::build(&def) } /// Round-trip Ixon.Recursor. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_recursor( - obj: LeanIxonRecursor, -) -> LeanIxonRecursor { + obj: LeanIxonRecursor>, +) -> LeanIxonRecursor { let rec = obj.decode(); LeanIxonRecursor::build(&rec) } /// Round-trip Ixon.Axiom. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ixon_axiom(obj: LeanIxonAxiom) -> LeanIxonAxiom { +pub extern "C" fn rs_roundtrip_ixon_axiom( + obj: LeanIxonAxiom>, +) -> LeanIxonAxiom { let ax = obj.decode(); LeanIxonAxiom::build(&ax) } /// Round-trip Ixon.Quotient. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_quotient( - obj: LeanIxonQuotient, -) -> LeanIxonQuotient { + obj: LeanIxonQuotient>, +) -> LeanIxonQuotient { let quot = obj.decode(); LeanIxonQuotient::build(") } /// Round-trip Ixon.ConstantInfo. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_constant_info( - obj: LeanIxonConstantInfo, -) -> LeanIxonConstantInfo { + obj: LeanIxonConstantInfo>, +) -> LeanIxonConstantInfo { let info = obj.decode(); LeanIxonConstantInfo::build(&info) } /// Round-trip Ixon.Constant. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_constant( - obj: LeanIxonConstant, -) -> LeanIxonConstant { + obj: LeanIxonConstant>, +) -> LeanIxonConstant { let constant = obj.decode(); LeanIxonConstant::build(&constant) } /// Round-trip Ixon.RecursorRule. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_recursor_rule( - obj: LeanIxonRecursorRule, -) -> LeanIxonRecursorRule { + obj: LeanIxonRecursorRule>, +) -> LeanIxonRecursorRule { let rule = obj.decode(); LeanIxonRecursorRule::build(&rule) } /// Round-trip Ixon.Constructor. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_constructor( - obj: LeanIxonConstructor, -) -> LeanIxonConstructor { + obj: LeanIxonConstructor>, +) -> LeanIxonConstructor { let c = obj.decode(); LeanIxonConstructor::build(&c) } /// Round-trip Ixon.Inductive. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_inductive( - obj: LeanIxonInductive, -) -> LeanIxonInductive { + obj: LeanIxonInductive>, +) -> LeanIxonInductive { let ind = obj.decode(); LeanIxonInductive::build(&ind) } /// Round-trip Ixon.InductiveProj. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_inductive_proj( - obj: LeanIxonInductiveProj, -) -> LeanIxonInductiveProj { + obj: LeanIxonInductiveProj>, +) -> LeanIxonInductiveProj { let proj = obj.decode(); LeanIxonInductiveProj::build(&proj) } /// Round-trip Ixon.ConstructorProj. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_constructor_proj( - obj: LeanIxonConstructorProj, -) -> LeanIxonConstructorProj { + obj: LeanIxonConstructorProj>, +) -> LeanIxonConstructorProj { let proj = obj.decode(); LeanIxonConstructorProj::build(&proj) } /// Round-trip Ixon.RecursorProj. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_recursor_proj( - obj: LeanIxonRecursorProj, -) -> LeanIxonRecursorProj { + obj: LeanIxonRecursorProj>, +) -> LeanIxonRecursorProj { let proj = obj.decode(); LeanIxonRecursorProj::build(&proj) } /// Round-trip Ixon.DefinitionProj. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_definition_proj( - obj: LeanIxonDefinitionProj, -) -> LeanIxonDefinitionProj { + obj: LeanIxonDefinitionProj>, +) -> LeanIxonDefinitionProj { let proj = obj.decode(); LeanIxonDefinitionProj::build(&proj) } /// Round-trip Ixon.MutConst. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_mut_const( - obj: LeanIxonMutConst, -) -> LeanIxonMutConst { + obj: LeanIxonMutConst>, +) -> LeanIxonMutConst { let mc = obj.decode(); LeanIxonMutConst::build(&mc) } diff --git a/src/ffi/ixon/enums.rs b/src/ffi/ixon/enums.rs index a1fb3e55..3a3d0721 100644 --- a/src/ffi/ixon/enums.rs +++ b/src/ffi/ixon/enums.rs @@ -5,9 +5,11 @@ use crate::ix::ixon::constant::DefKind; use crate::lean::{ LeanIxonDefKind, LeanIxonDefinitionSafety, LeanIxonQuotKind, }; -use lean_ffi::object::LeanObject; +use lean_ffi::object::{LeanOwned, LeanRef}; +#[cfg(feature = "test-ffi")] +use lean_ffi::object::LeanBorrowed; -impl LeanIxonDefKind { +impl LeanIxonDefKind { /// Build Ixon.DefKind /// | defn -- tag 0 /// | opaq -- tag 1 @@ -19,12 +21,14 @@ impl LeanIxonDefKind { DefKind::Opaque => 1, DefKind::Theorem => 2, }; - Self::new(LeanObject::from_enum_tag(tag)) + Self::new(LeanOwned::from_enum_tag(tag)) } +} +impl LeanIxonDefKind { /// Decode Ixon.DefKind (simple enum, raw unboxed tag value). - pub fn decode(self) -> DefKind { - let tag = self.as_enum_tag(); + pub fn decode(&self) -> DefKind { + let tag = self.inner().as_enum_tag(); match tag { 0 => DefKind::Definition, 1 => DefKind::Opaque, @@ -34,7 +38,7 @@ impl LeanIxonDefKind { } } -impl LeanIxonDefinitionSafety { +impl LeanIxonDefinitionSafety { /// Build Ixon.DefinitionSafety /// | unsaf -- tag 0 /// | safe -- tag 1 @@ -45,12 +49,14 @@ impl LeanIxonDefinitionSafety { DefinitionSafety::Safe => 1, DefinitionSafety::Partial => 2, }; - Self::new(LeanObject::from_enum_tag(tag)) + Self::new(LeanOwned::from_enum_tag(tag)) } +} +impl LeanIxonDefinitionSafety { /// Decode Ixon.DefinitionSafety (simple enum, raw unboxed tag value). - pub fn decode(self) -> DefinitionSafety { - let tag = self.as_enum_tag(); + pub fn decode(&self) -> DefinitionSafety { + let tag = self.inner().as_enum_tag(); match tag { 0 => DefinitionSafety::Unsafe, 1 => DefinitionSafety::Safe, @@ -60,7 +66,7 @@ impl LeanIxonDefinitionSafety { } } -impl LeanIxonQuotKind { +impl LeanIxonQuotKind { /// Build Ixon.QuotKind /// | type -- tag 0 /// | ctor -- tag 1 @@ -73,12 +79,14 @@ impl LeanIxonQuotKind { QuotKind::Lift => 2, QuotKind::Ind => 3, }; - Self::new(LeanObject::from_enum_tag(tag)) + Self::new(LeanOwned::from_enum_tag(tag)) } +} +impl LeanIxonQuotKind { /// Decode Ixon.QuotKind (simple enum, raw unboxed tag value). - pub fn decode(self) -> QuotKind { - let tag = self.as_enum_tag(); + pub fn decode(&self) -> QuotKind { + let tag = self.inner().as_enum_tag(); match tag { 0 => QuotKind::Type, 1 => QuotKind::Ctor, @@ -94,28 +102,31 @@ impl LeanIxonQuotKind { // ============================================================================= /// Round-trip Ixon.DefKind. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_def_kind( - obj: LeanIxonDefKind, -) -> LeanIxonDefKind { + obj: LeanIxonDefKind>, +) -> LeanIxonDefKind { let kind = obj.decode(); LeanIxonDefKind::build(&kind) } /// Round-trip Ixon.DefinitionSafety. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_definition_safety( - obj: LeanIxonDefinitionSafety, -) -> LeanIxonDefinitionSafety { + obj: LeanIxonDefinitionSafety>, +) -> LeanIxonDefinitionSafety { let safety = obj.decode(); LeanIxonDefinitionSafety::build(&safety) } /// Round-trip Ixon.QuotKind. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_quot_kind( - obj: LeanIxonQuotKind, -) -> LeanIxonQuotKind { + obj: LeanIxonQuotKind>, +) -> LeanIxonQuotKind { let kind = obj.decode(); LeanIxonQuotKind::build(&kind) } diff --git a/src/ffi/ixon/env.rs b/src/ffi/ixon/env.rs index 5b312e8c..91322697 100644 --- a/src/ffi/ixon/env.rs +++ b/src/ffi/ixon/env.rs @@ -14,7 +14,10 @@ use crate::lean::{ LeanIxonRawBlob, LeanIxonRawComm, LeanIxonRawConst, LeanIxonRawEnv, LeanIxonRawNameEntry, LeanIxonRawNamed, }; -use lean_ffi::object::{LeanArray, LeanByteArray, LeanCtor, LeanExcept}; +use lean_ffi::object::{ + LeanArray, LeanBorrowed, LeanByteArray, LeanCtor, LeanExcept, LeanOwned, + LeanRef, +}; use crate::ffi::builder::LeanBuildCache; use crate::lean::LeanIxAddress; @@ -29,22 +32,13 @@ pub struct DecodedRawConst { pub constant: IxonConstant, } -impl LeanIxonRawConst { - /// Decode Ixon.RawConst from Lean pointer. - pub fn decode(self) -> DecodedRawConst { - let ctor = self.as_ctor(); - DecodedRawConst { - addr: LeanIxAddress::new(ctor.get(0)).decode(), - constant: LeanIxonConstant::new(ctor.get(1)).decode(), - } - } - +impl LeanIxonRawConst { /// Build Ixon.RawConst Lean object. pub fn build(rc: &DecodedRawConst) -> Self { let ctor = LeanCtor::alloc(0, 2, 0); ctor.set(0, LeanIxAddress::build(&rc.addr)); ctor.set(1, LeanIxonConstant::build(&rc.constant)); - Self::new(*ctor) + Self::new(ctor.into()) } /// Build from individual parts (used by compile.rs). @@ -52,7 +46,18 @@ impl LeanIxonRawConst { let ctor = LeanCtor::alloc(0, 2, 0); ctor.set(0, LeanIxAddress::build(addr)); ctor.set(1, LeanIxonConstant::build(constant)); - Self::new(*ctor) + Self::new(ctor.into()) + } +} + +impl LeanIxonRawConst { + /// Decode Ixon.RawConst from Lean pointer. + pub fn decode(&self) -> DecodedRawConst { + let ctor = self.as_ctor(); + DecodedRawConst { + addr: LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + constant: LeanIxonConstant::new(ctor.get(1).to_owned_ref()).decode(), + } } } @@ -67,24 +72,14 @@ pub struct DecodedRawNamed { pub const_meta: ConstantMeta, } -impl LeanIxonRawNamed { - /// Decode Ixon.RawNamed from Lean pointer. - pub fn decode(self) -> DecodedRawNamed { - let ctor = self.as_ctor(); - DecodedRawNamed { - name: LeanIxName::new(ctor.get(0)).decode(), - addr: LeanIxAddress::new(ctor.get(1)).decode(), - const_meta: LeanIxonConstantMeta::new(ctor.get(2)).decode(), - } - } - +impl LeanIxonRawNamed { /// Build Ixon.RawNamed Lean object. pub fn build(cache: &mut LeanBuildCache, rn: &DecodedRawNamed) -> Self { let ctor = LeanCtor::alloc(0, 3, 0); ctor.set(0, LeanIxName::build(cache, &rn.name)); ctor.set(1, LeanIxAddress::build(&rn.addr)); ctor.set(2, LeanIxonConstantMeta::build(&rn.const_meta)); - Self::new(*ctor) + Self::new(ctor.into()) } /// Build from individual parts (used by compile.rs). @@ -98,7 +93,20 @@ impl LeanIxonRawNamed { ctor.set(0, LeanIxName::build(cache, name)); ctor.set(1, LeanIxAddress::build(addr)); ctor.set(2, LeanIxonConstantMeta::build(meta)); - Self::new(*ctor) + Self::new(ctor.into()) + } +} + +impl LeanIxonRawNamed { + /// Decode Ixon.RawNamed from Lean pointer. + pub fn decode(&self) -> DecodedRawNamed { + let ctor = self.as_ctor(); + DecodedRawNamed { + name: LeanIxName::new(ctor.get(0).to_owned_ref()).decode(), + addr: LeanIxAddress::from_borrowed(ctor.get(1).as_byte_array()).decode(), + const_meta: LeanIxonConstantMeta::new(ctor.get(2).to_owned_ref()) + .decode(), + } } } @@ -112,23 +120,13 @@ pub struct DecodedRawBlob { pub bytes: Vec, } -impl LeanIxonRawBlob { - /// Decode Ixon.RawBlob from Lean pointer. - pub fn decode(self) -> DecodedRawBlob { - let ctor = self.as_ctor(); - let ba = ctor.get(1).as_byte_array(); - DecodedRawBlob { - addr: LeanIxAddress::new(ctor.get(0)).decode(), - bytes: ba.as_bytes().to_vec(), - } - } - +impl LeanIxonRawBlob { /// Build Ixon.RawBlob Lean object. pub fn build(rb: &DecodedRawBlob) -> Self { let ctor = LeanCtor::alloc(0, 2, 0); ctor.set(0, LeanIxAddress::build(&rb.addr)); ctor.set(1, LeanByteArray::from_bytes(&rb.bytes)); - Self::new(*ctor) + Self::new(ctor.into()) } /// Build from individual parts (used by compile.rs). @@ -136,7 +134,19 @@ impl LeanIxonRawBlob { let ctor = LeanCtor::alloc(0, 2, 0); ctor.set(0, LeanIxAddress::build(addr)); ctor.set(1, LeanByteArray::from_bytes(bytes)); - Self::new(*ctor) + Self::new(ctor.into()) + } +} + +impl LeanIxonRawBlob { + /// Decode Ixon.RawBlob from Lean pointer. + pub fn decode(&self) -> DecodedRawBlob { + let ctor = self.as_ctor(); + let ba = ctor.get(1).as_byte_array(); + DecodedRawBlob { + addr: LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + bytes: ba.as_bytes().to_vec(), + } } } @@ -150,22 +160,13 @@ pub struct DecodedRawComm { pub comm: Comm, } -impl LeanIxonRawComm { - /// Decode Ixon.RawComm from Lean pointer. - pub fn decode(self) -> DecodedRawComm { - let ctor = self.as_ctor(); - DecodedRawComm { - addr: LeanIxAddress::new(ctor.get(0)).decode(), - comm: LeanIxonComm::new(ctor.get(1)).decode(), - } - } - +impl LeanIxonRawComm { /// Build Ixon.RawComm Lean object. pub fn build(rc: &DecodedRawComm) -> Self { let ctor = LeanCtor::alloc(0, 2, 0); ctor.set(0, LeanIxAddress::build(&rc.addr)); ctor.set(1, LeanIxonComm::build(&rc.comm)); - Self::new(*ctor) + Self::new(ctor.into()) } /// Build from individual parts (used by compile.rs). @@ -173,7 +174,18 @@ impl LeanIxonRawComm { let ctor = LeanCtor::alloc(0, 2, 0); ctor.set(0, LeanIxAddress::build(addr)); ctor.set(1, LeanIxonComm::build(comm)); - Self::new(*ctor) + Self::new(ctor.into()) + } +} + +impl LeanIxonRawComm { + /// Decode Ixon.RawComm from Lean pointer. + pub fn decode(&self) -> DecodedRawComm { + let ctor = self.as_ctor(); + DecodedRawComm { + addr: LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + comm: LeanIxonComm::new(ctor.get(1).to_owned_ref()).decode(), + } } } @@ -187,16 +199,7 @@ pub struct DecodedRawNameEntry { pub name: Name, } -impl LeanIxonRawNameEntry { - /// Decode Ixon.RawNameEntry from Lean pointer. - pub fn decode(self) -> DecodedRawNameEntry { - let ctor = self.as_ctor(); - DecodedRawNameEntry { - addr: LeanIxAddress::new(ctor.get(0)).decode(), - name: LeanIxName::new(ctor.get(1)).decode(), - } - } - +impl LeanIxonRawNameEntry { /// Build Ixon.RawNameEntry Lean object. pub fn build( cache: &mut LeanBuildCache, @@ -206,7 +209,18 @@ impl LeanIxonRawNameEntry { let ctor = LeanCtor::alloc(0, 2, 0); ctor.set(0, LeanIxAddress::build(addr)); ctor.set(1, LeanIxName::build(cache, name)); - Self::new(*ctor) + Self::new(ctor.into()) + } +} + +impl LeanIxonRawNameEntry { + /// Decode Ixon.RawNameEntry from Lean pointer. + pub fn decode(&self) -> DecodedRawNameEntry { + let ctor = self.as_ctor(); + DecodedRawNameEntry { + addr: LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + name: LeanIxName::new(ctor.get(1).to_owned_ref()).decode(), + } } } @@ -223,25 +237,7 @@ pub struct DecodedRawEnv { pub names: Vec, } -impl LeanIxonRawEnv { - /// Decode Ixon.RawEnv from Lean pointer. - pub fn decode(self) -> DecodedRawEnv { - let ctor = self.as_ctor(); - let consts_arr = ctor.get(0).as_array(); - let named_arr = ctor.get(1).as_array(); - let blobs_arr = ctor.get(2).as_array(); - let comms_arr = ctor.get(3).as_array(); - let names_arr = ctor.get(4).as_array(); - - DecodedRawEnv { - consts: consts_arr.map(|x| LeanIxonRawConst::new(x).decode()), - named: named_arr.map(|x| LeanIxonRawNamed::new(x).decode()), - blobs: blobs_arr.map(|x| LeanIxonRawBlob::new(x).decode()), - comms: comms_arr.map(|x| LeanIxonRawComm::new(x).decode()), - names: names_arr.map(|x| LeanIxonRawNameEntry::new(x).decode()), - } - } - +impl LeanIxonRawEnv { /// Build Ixon.RawEnv Lean object. pub fn build(env: &DecodedRawEnv) -> Self { let mut cache = LeanBuildCache::new(); @@ -284,7 +280,30 @@ impl LeanIxonRawEnv { ctor.set(2, blobs_arr); ctor.set(3, comms_arr); ctor.set(4, names_arr); - Self::new(*ctor) + Self::new(ctor.into()) + } +} + +impl LeanIxonRawEnv { + /// Decode Ixon.RawEnv from Lean pointer. + pub fn decode(&self) -> DecodedRawEnv { + let ctor = self.as_ctor(); + let consts_arr = ctor.get(0).as_array(); + let named_arr = ctor.get(1).as_array(); + let blobs_arr = ctor.get(2).as_array(); + let comms_arr = ctor.get(3).as_array(); + let names_arr = ctor.get(4).as_array(); + + DecodedRawEnv { + consts: consts_arr + .map(|x| LeanIxonRawConst::new(x.to_owned_ref()).decode()), + named: named_arr + .map(|x| LeanIxonRawNamed::new(x.to_owned_ref()).decode()), + blobs: blobs_arr.map(|x| LeanIxonRawBlob::new(x.to_owned_ref()).decode()), + comms: comms_arr.map(|x| LeanIxonRawComm::new(x.to_owned_ref()).decode()), + names: names_arr + .map(|x| LeanIxonRawNameEntry::new(x.to_owned_ref()).decode()), + } } } @@ -360,7 +379,9 @@ pub fn ixon_env_to_decoded(env: &IxonEnv) -> DecodedRawEnv { /// FFI: Serialize an Ixon.RawEnv -> ByteArray via Rust's Env.put. Pure. #[unsafe(no_mangle)] -pub extern "C" fn rs_ser_env(obj: LeanIxonRawEnv) -> LeanByteArray { +pub extern "C" fn rs_ser_env( + obj: LeanIxonRawEnv>, +) -> LeanByteArray { let decoded = obj.decode(); let env = decoded_to_ixon_env(&decoded); let mut buf = Vec::new(); @@ -375,7 +396,9 @@ pub extern "C" fn rs_ser_env(obj: LeanIxonRawEnv) -> LeanByteArray { /// FFI: Deserialize ByteArray -> Except String Ixon.RawEnv via Rust's Env.get. Pure. #[unsafe(no_mangle)] -pub extern "C" fn rs_de_env(obj: LeanByteArray) -> LeanExcept { +pub extern "C" fn rs_de_env( + obj: LeanByteArray>, +) -> LeanExcept { let data = obj.as_bytes(); let mut slice: &[u8] = data; match IxonEnv::get(&mut slice) { diff --git a/src/ffi/ixon/expr.rs b/src/ffi/ixon/expr.rs index add908f4..fcbecaf5 100644 --- a/src/ffi/ixon/expr.rs +++ b/src/ffi/ixon/expr.rs @@ -4,10 +4,10 @@ use std::sync::Arc; use crate::ix::ixon::expr::Expr as IxonExpr; use crate::lean::LeanIxonExpr; -use lean_ffi::object::{LeanArray, LeanCtor}; +use lean_ffi::object::{LeanArray, LeanBorrowed, LeanCtor, LeanOwned, LeanRef}; /// Decode Array UInt64 from Lean. -fn decode_u64_array(obj: LeanArray) -> Vec { +fn decode_u64_array(obj: LeanArray>) -> Vec { obj .iter() .map(|elem| { @@ -15,67 +15,67 @@ fn decode_u64_array(obj: LeanArray) -> Vec { elem.unbox_usize() as u64 } else { let ctor = elem.as_ctor(); - ctor.scalar_u64(0, 0) + ctor.get_u64(0, 0) } }) .collect() } -impl LeanIxonExpr { +impl LeanIxonExpr { /// Build Ixon.Expr (12 constructors). pub fn build(expr: &IxonExpr) -> Self { let obj = match expr { IxonExpr::Sort(idx) => { let ctor = LeanCtor::alloc(0, 0, 8); - ctor.set_scalar_u64(0, 0, *idx); - *ctor + ctor.set_u64(0, 0, *idx); + ctor.into() }, IxonExpr::Var(idx) => { let ctor = LeanCtor::alloc(1, 0, 8); - ctor.set_scalar_u64(0, 0, *idx); - *ctor + ctor.set_u64(0, 0, *idx); + ctor.into() }, IxonExpr::Ref(ref_idx, univ_idxs) => { let arr = LeanArray::alloc(univ_idxs.len()); for (i, idx) in univ_idxs.iter().enumerate() { let uint64_obj = LeanCtor::alloc(0, 0, 8); - uint64_obj.set_scalar_u64(0, 0, *idx); + uint64_obj.set_u64(0, 0, *idx); arr.set(i, uint64_obj); } let ctor = LeanCtor::alloc(2, 1, 8); ctor.set(0, arr); - ctor.set_scalar_u64(1, 0, *ref_idx); - *ctor + ctor.set_u64(1, 0, *ref_idx); + ctor.into() }, IxonExpr::Rec(rec_idx, univ_idxs) => { let arr = LeanArray::alloc(univ_idxs.len()); for (i, idx) in univ_idxs.iter().enumerate() { let uint64_obj = LeanCtor::alloc(0, 0, 8); - uint64_obj.set_scalar_u64(0, 0, *idx); + uint64_obj.set_u64(0, 0, *idx); arr.set(i, uint64_obj); } let ctor = LeanCtor::alloc(3, 1, 8); ctor.set(0, arr); - ctor.set_scalar_u64(1, 0, *rec_idx); - *ctor + ctor.set_u64(1, 0, *rec_idx); + ctor.into() }, IxonExpr::Prj(type_ref_idx, field_idx, val) => { let val_obj = Self::build(val); let ctor = LeanCtor::alloc(4, 1, 16); ctor.set(0, val_obj); - ctor.set_scalar_u64(1, 0, *type_ref_idx); - ctor.set_scalar_u64(1, 8, *field_idx); - *ctor + ctor.set_u64(1, 0, *type_ref_idx); + ctor.set_u64(1, 8, *field_idx); + ctor.into() }, IxonExpr::Str(ref_idx) => { let ctor = LeanCtor::alloc(5, 0, 8); - ctor.set_scalar_u64(0, 0, *ref_idx); - *ctor + ctor.set_u64(0, 0, *ref_idx); + ctor.into() }, IxonExpr::Nat(ref_idx) => { let ctor = LeanCtor::alloc(6, 0, 8); - ctor.set_scalar_u64(0, 0, *ref_idx); - *ctor + ctor.set_u64(0, 0, *ref_idx); + ctor.into() }, IxonExpr::App(fun, arg) => { let fun_obj = Self::build(fun); @@ -83,7 +83,7 @@ impl LeanIxonExpr { let ctor = LeanCtor::alloc(7, 2, 0); ctor.set(0, fun_obj); ctor.set(1, arg_obj); - *ctor + ctor.into() }, IxonExpr::Lam(ty, body) => { let ty_obj = Self::build(ty); @@ -91,7 +91,7 @@ impl LeanIxonExpr { let ctor = LeanCtor::alloc(8, 2, 0); ctor.set(0, ty_obj); ctor.set(1, body_obj); - *ctor + ctor.into() }, IxonExpr::All(ty, body) => { let ty_obj = Self::build(ty); @@ -99,7 +99,7 @@ impl LeanIxonExpr { let ctor = LeanCtor::alloc(9, 2, 0); ctor.set(0, ty_obj); ctor.set(1, body_obj); - *ctor + ctor.into() }, IxonExpr::Let(non_dep, ty, val, body) => { let ty_obj = Self::build(ty); @@ -109,102 +109,103 @@ impl LeanIxonExpr { ctor.set(0, ty_obj); ctor.set(1, val_obj); ctor.set(2, body_obj); - ctor.set_scalar_u8(3, 0, if *non_dep { 1 } else { 0 }); - *ctor + ctor.set_u8(3, 0, if *non_dep { 1 } else { 0 }); + ctor.into() }, IxonExpr::Share(idx) => { let ctor = LeanCtor::alloc(11, 0, 8); - ctor.set_scalar_u64(0, 0, *idx); - *ctor + ctor.set_u64(0, 0, *idx); + ctor.into() }, }; Self::new(obj) } /// Build an Array of Ixon.Expr. - pub fn build_array(exprs: &[Arc]) -> LeanArray { + pub fn build_array(exprs: &[Arc]) -> LeanArray { let arr = LeanArray::alloc(exprs.len()); for (i, expr) in exprs.iter().enumerate() { arr.set(i, Self::build(expr)); } arr } +} +impl LeanIxonExpr { /// Decode Ixon.Expr (12 constructors). - pub fn decode(self) -> IxonExpr { + pub fn decode(&self) -> IxonExpr { let ctor = self.as_ctor(); let tag = ctor.tag(); match tag { 0 => { - let idx = ctor.scalar_u64(0, 0); + let idx = ctor.get_u64(0, 0); IxonExpr::Sort(idx) }, 1 => { - let idx = ctor.scalar_u64(0, 0); + let idx = ctor.get_u64(0, 0); IxonExpr::Var(idx) }, 2 => { - let ref_idx = ctor.scalar_u64(1, 0); + let ref_idx = ctor.get_u64(1, 0); let univ_idxs = decode_u64_array(ctor.get(0).as_array()); IxonExpr::Ref(ref_idx, univ_idxs) }, 3 => { - let rec_idx = ctor.scalar_u64(1, 0); + let rec_idx = ctor.get_u64(1, 0); let univ_idxs = decode_u64_array(ctor.get(0).as_array()); IxonExpr::Rec(rec_idx, univ_idxs) }, 4 => { - let val_obj = Self::new(ctor.get(0)); - let type_ref_idx = ctor.scalar_u64(1, 0); - let field_idx = ctor.scalar_u64(1, 8); - IxonExpr::Prj(type_ref_idx, field_idx, Arc::new(val_obj.decode())) + let type_ref_idx = ctor.get_u64(1, 0); + let field_idx = ctor.get_u64(1, 8); + IxonExpr::Prj( + type_ref_idx, + field_idx, + Arc::new(LeanIxonExpr(ctor.get(0)).decode()), + ) }, 5 => { - let ref_idx = ctor.scalar_u64(0, 0); + let ref_idx = ctor.get_u64(0, 0); IxonExpr::Str(ref_idx) }, 6 => { - let ref_idx = ctor.scalar_u64(0, 0); + let ref_idx = ctor.get_u64(0, 0); IxonExpr::Nat(ref_idx) }, - 7 => { - let f_obj = Self::new(ctor.get(0)); - let a_obj = Self::new(ctor.get(1)); - IxonExpr::App(Arc::new(f_obj.decode()), Arc::new(a_obj.decode())) - }, - 8 => { - let ty_obj = Self::new(ctor.get(0)); - let body_obj = Self::new(ctor.get(1)); - IxonExpr::Lam(Arc::new(ty_obj.decode()), Arc::new(body_obj.decode())) - }, - 9 => { - let ty_obj = Self::new(ctor.get(0)); - let body_obj = Self::new(ctor.get(1)); - IxonExpr::All(Arc::new(ty_obj.decode()), Arc::new(body_obj.decode())) - }, + 7 => IxonExpr::App( + Arc::new(LeanIxonExpr(ctor.get(0)).decode()), + Arc::new(LeanIxonExpr(ctor.get(1)).decode()), + ), + 8 => IxonExpr::Lam( + Arc::new(LeanIxonExpr(ctor.get(0)).decode()), + Arc::new(LeanIxonExpr(ctor.get(1)).decode()), + ), + 9 => IxonExpr::All( + Arc::new(LeanIxonExpr(ctor.get(0)).decode()), + Arc::new(LeanIxonExpr(ctor.get(1)).decode()), + ), 10 => { - let ty_obj = Self::new(ctor.get(0)); - let val_obj = Self::new(ctor.get(1)); - let body_obj = Self::new(ctor.get(2)); - let non_dep = ctor.scalar_u8(3, 0) != 0; + let non_dep = ctor.get_u8(3, 0) != 0; IxonExpr::Let( non_dep, - Arc::new(ty_obj.decode()), - Arc::new(val_obj.decode()), - Arc::new(body_obj.decode()), + Arc::new(LeanIxonExpr(ctor.get(0)).decode()), + Arc::new(LeanIxonExpr(ctor.get(1)).decode()), + Arc::new(LeanIxonExpr(ctor.get(2)).decode()), ) }, 11 => { - let idx = ctor.scalar_u64(0, 0); + let idx = ctor.get_u64(0, 0); IxonExpr::Share(idx) }, _ => panic!("Invalid Ixon.Expr tag: {}", tag), } } +} +impl LeanIxonExpr { /// Decode Array Ixon.Expr. - pub fn decode_array(obj: LeanArray) -> Vec> { - obj.map(|e| Arc::new(Self::new(e).decode())) + pub fn decode_array(obj: &LeanArray) -> Vec> { + obj.map(|e| Arc::new(LeanIxonExpr(e).decode())) } } @@ -213,8 +214,11 @@ impl LeanIxonExpr { // ============================================================================= /// Round-trip Ixon.Expr. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ixon_expr(obj: LeanIxonExpr) -> LeanIxonExpr { +pub extern "C" fn rs_roundtrip_ixon_expr( + obj: LeanIxonExpr>, +) -> LeanIxonExpr { let expr = obj.decode(); LeanIxonExpr::build(&expr) } diff --git a/src/ffi/ixon/meta.rs b/src/ffi/ixon/meta.rs index c8ee530b..d853bca6 100644 --- a/src/ffi/ixon/meta.rs +++ b/src/ffi/ixon/meta.rs @@ -14,7 +14,7 @@ use crate::lean::{ LeanIxonDataValue, LeanIxonExprMetaArena, LeanIxonExprMetaData, LeanIxonNamed, }; -use lean_ffi::object::{LeanArray, LeanCtor, LeanObject}; +use lean_ffi::object::{LeanArray, LeanBorrowed, LeanCtor, LeanOwned, LeanRef}; use crate::lean::LeanIxAddress; use crate::lean::LeanIxBinderInfo; @@ -24,7 +24,7 @@ use crate::lean::LeanIxBinderInfo; // ============================================================================= /// Build an Ixon.KVMap (Array (Address × DataValue)). -pub fn build_ixon_kvmap(kvmap: &KVMap) -> LeanArray { +pub fn build_ixon_kvmap(kvmap: &KVMap) -> LeanArray { let arr = LeanArray::alloc(kvmap.len()); for (i, (addr, dv)) in kvmap.iter().enumerate() { let pair = LeanCtor::alloc(0, 2, 0); @@ -36,7 +36,7 @@ pub fn build_ixon_kvmap(kvmap: &KVMap) -> LeanArray { } /// Build Array KVMap. -pub fn build_kvmap_array(kvmaps: &[KVMap]) -> LeanArray { +pub fn build_kvmap_array(kvmaps: &[KVMap]) -> LeanArray { let arr = LeanArray::alloc(kvmaps.len()); for (i, kvmap) in kvmaps.iter().enumerate() { arr.set(i, build_ixon_kvmap(kvmap)); @@ -45,21 +45,21 @@ pub fn build_kvmap_array(kvmaps: &[KVMap]) -> LeanArray { } /// Decode KVMap (Array (Address × DataValue)). -pub fn decode_ixon_kvmap(obj: LeanArray) -> KVMap { +pub fn decode_ixon_kvmap(obj: LeanArray>) -> KVMap { obj .iter() .map(|pair| { let pair_ctor = pair.as_ctor(); ( - LeanIxAddress::new(pair_ctor.get(0)).decode(), - LeanIxonDataValue::new(pair_ctor.get(1)).decode(), + LeanIxAddress::from_borrowed(pair_ctor.get(0).as_byte_array()).decode(), + LeanIxonDataValue::new(pair_ctor.get(1).to_owned_ref()).decode(), ) }) .collect() } /// Decode Array KVMap. -fn decode_kvmap_array(obj: LeanArray) -> Vec { +fn decode_kvmap_array(obj: LeanArray>) -> Vec { obj.map(|x| decode_ixon_kvmap(x.as_array())) } @@ -68,21 +68,21 @@ fn decode_kvmap_array(obj: LeanArray) -> Vec { // ============================================================================= /// Decode Array Address. -fn decode_address_array(obj: LeanArray) -> Vec
{ +fn decode_address_array(obj: LeanArray>) -> Vec
{ LeanIxAddress::decode_array(obj) } /// Build Array UInt64. -fn build_u64_array(vals: &[u64]) -> LeanArray { +fn build_u64_array(vals: &[u64]) -> LeanArray { let arr = LeanArray::alloc(vals.len()); for (i, &v) in vals.iter().enumerate() { - arr.set(i, LeanObject::box_u64(v)); + arr.set(i, LeanOwned::box_u64(v)); } arr } /// Decode Array UInt64. -fn decode_u64_array(obj: LeanArray) -> Vec { +fn decode_u64_array(obj: LeanArray>) -> Vec { obj.iter().map(|elem| elem.unbox_u64()).collect() } @@ -90,57 +90,69 @@ fn decode_u64_array(obj: LeanArray) -> Vec { // DataValue Build/Decode // ============================================================================= -impl LeanIxonDataValue { +impl LeanIxonDataValue { /// Build Ixon.DataValue (for metadata) pub fn build(dv: &IxonDataValue) -> Self { let obj = match dv { IxonDataValue::OfString(addr) => { let ctor = LeanCtor::alloc(0, 1, 0); ctor.set(0, LeanIxAddress::build(addr)); - *ctor + ctor.into() }, IxonDataValue::OfBool(b) => { let ctor = LeanCtor::alloc(1, 0, 1); - ctor.set_scalar_u8(0, 0, if *b { 1 } else { 0 }); - *ctor + ctor.set_u8(0, 0, if *b { 1 } else { 0 }); + ctor.into() }, IxonDataValue::OfName(addr) => { let ctor = LeanCtor::alloc(2, 1, 0); ctor.set(0, LeanIxAddress::build(addr)); - *ctor + ctor.into() }, IxonDataValue::OfNat(addr) => { let ctor = LeanCtor::alloc(3, 1, 0); ctor.set(0, LeanIxAddress::build(addr)); - *ctor + ctor.into() }, IxonDataValue::OfInt(addr) => { let ctor = LeanCtor::alloc(4, 1, 0); ctor.set(0, LeanIxAddress::build(addr)); - *ctor + ctor.into() }, IxonDataValue::OfSyntax(addr) => { let ctor = LeanCtor::alloc(5, 1, 0); ctor.set(0, LeanIxAddress::build(addr)); - *ctor + ctor.into() }, }; Self::new(obj) } +} +impl LeanIxonDataValue { /// Decode Ixon.DataValue. - pub fn decode(self) -> IxonDataValue { + pub fn decode(&self) -> IxonDataValue { let ctor = self.as_ctor(); match ctor.tag() { - 0 => IxonDataValue::OfString(LeanIxAddress::new(ctor.get(0)).decode()), + 0 => IxonDataValue::OfString( + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + ), 1 => { - let b = ctor.scalar_u8(0, 0) != 0; + let b = ctor.get_u8(0, 0) != 0; IxonDataValue::OfBool(b) }, - 2 => IxonDataValue::OfName(LeanIxAddress::new(ctor.get(0)).decode()), - 3 => IxonDataValue::OfNat(LeanIxAddress::new(ctor.get(0)).decode()), - 4 => IxonDataValue::OfInt(LeanIxAddress::new(ctor.get(0)).decode()), - 5 => IxonDataValue::OfSyntax(LeanIxAddress::new(ctor.get(0)).decode()), + 2 => IxonDataValue::OfName( + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + ), + 3 => IxonDataValue::OfNat( + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + ), + 4 => IxonDataValue::OfInt( + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + ), + 5 => IxonDataValue::OfSyntax( + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + ), tag => panic!("Invalid Ixon.DataValue tag: {}", tag), } } @@ -150,7 +162,7 @@ impl LeanIxonDataValue { // ExprMetaData Build/Decode // ============================================================================= -impl LeanIxonExprMetaData { +impl LeanIxonExprMetaData { /// Build Ixon.ExprMetaData Lean object. /// /// | Variant | Tag | Obj fields | Scalar bytes | @@ -164,14 +176,14 @@ impl LeanIxonExprMetaData { /// | mdata | 6 | 1 (mdata: Array) | 8 (1× u64) | pub fn build(node: &ExprMetaData) -> Self { let obj = match node { - ExprMetaData::Leaf => LeanObject::box_usize(0), + ExprMetaData::Leaf => LeanOwned::box_usize(0), ExprMetaData::App { children } => { // Tag 1, 0 obj fields, 16 scalar bytes (2× u64) let ctor = LeanCtor::alloc(1, 0, 16); - ctor.set_scalar_u64(0, 0, children[0]); - ctor.set_scalar_u64(0, 8, children[1]); - *ctor + ctor.set_u64(0, 0, children[0]); + ctor.set_u64(0, 8, children[1]); + ctor.into() }, ExprMetaData::Binder { name, info, children } => { @@ -180,35 +192,35 @@ impl LeanIxonExprMetaData { // Offsets from obj_cptr: 1*8=8 base for scalar area let ctor = LeanCtor::alloc(2, 1, 17); ctor.set(0, LeanIxAddress::build(name)); - ctor.set_scalar_u64(1, 0, children[0]); - ctor.set_scalar_u64(1, 8, children[1]); - ctor.set_scalar_u8(1, 16, LeanIxBinderInfo::to_u8(info)); - *ctor + ctor.set_u64(1, 0, children[0]); + ctor.set_u64(1, 8, children[1]); + ctor.set_u8(1, 16, LeanIxBinderInfo::to_u8(info)); + ctor.into() }, ExprMetaData::LetBinder { name, children } => { // Tag 3, 1 obj field (name), 24 scalar bytes (3× u64) let ctor = LeanCtor::alloc(3, 1, 24); ctor.set(0, LeanIxAddress::build(name)); - ctor.set_scalar_u64(1, 0, children[0]); - ctor.set_scalar_u64(1, 8, children[1]); - ctor.set_scalar_u64(1, 16, children[2]); - *ctor + ctor.set_u64(1, 0, children[0]); + ctor.set_u64(1, 8, children[1]); + ctor.set_u64(1, 16, children[2]); + ctor.into() }, ExprMetaData::Ref { name } => { // Tag 4, 1 obj field (name), 0 scalar bytes let ctor = LeanCtor::alloc(4, 1, 0); ctor.set(0, LeanIxAddress::build(name)); - *ctor + ctor.into() }, ExprMetaData::Prj { struct_name, child } => { // Tag 5, 1 obj field (structName), 8 scalar bytes (1× u64) let ctor = LeanCtor::alloc(5, 1, 8); ctor.set(0, LeanIxAddress::build(struct_name)); - ctor.set_scalar_u64(1, 0, *child); - *ctor + ctor.set_u64(1, 0, *child); + ctor.into() }, ExprMetaData::Mdata { mdata, child } => { @@ -216,18 +228,20 @@ impl LeanIxonExprMetaData { let mdata_arr = build_kvmap_array(mdata); let ctor = LeanCtor::alloc(6, 1, 8); ctor.set(0, mdata_arr); - ctor.set_scalar_u64(1, 0, *child); - *ctor + ctor.set_u64(1, 0, *child); + ctor.into() }, }; Self::new(obj) } +} +impl LeanIxonExprMetaData { /// Decode Ixon.ExprMetaData from Lean pointer. - pub fn decode(self) -> ExprMetaData { + pub fn decode(&self) -> ExprMetaData { // Leaf (tag 0, no fields) is represented as a scalar lean_box(0) - if self.is_scalar() { - let tag = self.as_ptr() as usize >> 1; + if self.inner().is_scalar() { + let tag = self.inner().as_raw() as usize >> 1; assert_eq!(tag, 0, "Invalid scalar ExprMetaData tag: {}", tag); return ExprMetaData::Leaf; } @@ -235,18 +249,19 @@ impl LeanIxonExprMetaData { match ctor.tag() { 1 => { // app: 0 obj fields, 2× u64 scalar - let fun_ = ctor.scalar_u64(0, 0); - let arg = ctor.scalar_u64(0, 8); + let fun_ = ctor.get_u64(0, 0); + let arg = ctor.get_u64(0, 8); ExprMetaData::App { children: [fun_, arg] } }, 2 => { // binder: 1 obj field (name), scalar (Lean ABI: u64s first, then u8): // [tyChild: u64 @ 0] [bodyChild: u64 @ 8] [info: u8 @ 16] - let name = LeanIxAddress::new(ctor.get(0)).decode(); - let ty_child = ctor.scalar_u64(1, 0); - let body_child = ctor.scalar_u64(1, 8); - let info_byte = ctor.scalar_u8(1, 16); + let name = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); + let ty_child = ctor.get_u64(1, 0); + let body_child = ctor.get_u64(1, 8); + let info_byte = ctor.get_u8(1, 16); let info = match info_byte { 0 => BinderInfo::Default, 1 => BinderInfo::Implicit, @@ -259,10 +274,11 @@ impl LeanIxonExprMetaData { 3 => { // letBinder: 1 obj field (name), 3× u64 scalar - let name = LeanIxAddress::new(ctor.get(0)).decode(); - let ty_child = ctor.scalar_u64(1, 0); - let val_child = ctor.scalar_u64(1, 8); - let body_child = ctor.scalar_u64(1, 16); + let name = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); + let ty_child = ctor.get_u64(1, 0); + let val_child = ctor.get_u64(1, 8); + let body_child = ctor.get_u64(1, 16); ExprMetaData::LetBinder { name, children: [ty_child, val_child, body_child], @@ -271,20 +287,24 @@ impl LeanIxonExprMetaData { 4 => { // ref: 1 obj field (name), 0 scalar - ExprMetaData::Ref { name: LeanIxAddress::new(ctor.get(0)).decode() } + ExprMetaData::Ref { + name: LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()) + .decode(), + } }, 5 => { // prj: 1 obj field (structName), 1× u64 scalar - let struct_name = LeanIxAddress::new(ctor.get(0)).decode(); - let child = ctor.scalar_u64(1, 0); + let struct_name = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); + let child = ctor.get_u64(1, 0); ExprMetaData::Prj { struct_name, child } }, 6 => { // mdata: 1 obj field (mdata: Array KVMap), 1× u64 scalar let mdata = decode_kvmap_array(ctor.get(0).as_array()); - let child = ctor.scalar_u64(1, 0); + let child = ctor.get_u64(1, 0); ExprMetaData::Mdata { mdata, child } }, @@ -297,7 +317,7 @@ impl LeanIxonExprMetaData { // ExprMetaArena Build/Decode // ============================================================================= -impl LeanIxonExprMetaArena { +impl LeanIxonExprMetaArena { /// Build Ixon.ExprMetaArena Lean object. /// ExprMetaArena is a single-field structure (nodes : Array ExprMetaData), /// which Lean unboxes — the value IS the Array directly. @@ -306,14 +326,18 @@ impl LeanIxonExprMetaArena { for (i, node) in arena.nodes.iter().enumerate() { arr.set(i, LeanIxonExprMetaData::build(node)); } - Self::new(*arr) + Self::new(arr.into()) } +} +impl LeanIxonExprMetaArena { /// Decode Ixon.ExprMetaArena from Lean pointer. /// Single-field struct is unboxed — obj IS the Array directly. - pub fn decode(self) -> ExprMeta { - let arr = self.as_array(); - ExprMeta { nodes: arr.map(|x| LeanIxonExprMetaData::new(x).decode()) } + pub fn decode(&self) -> ExprMeta { + let arr = unsafe { LeanBorrowed::from_raw(self.as_raw()) }.as_array(); + ExprMeta { + nodes: arr.map(|x| LeanIxonExprMetaData::new(x.to_owned_ref()).decode()), + } } } @@ -321,7 +345,7 @@ impl LeanIxonExprMetaArena { // ConstantMeta Build/Decode // ============================================================================= -impl LeanIxonConstantMeta { +impl LeanIxonConstantMeta { /// Build Ixon.ConstantMeta Lean object. /// /// | Variant | Tag | Obj fields | Scalar bytes | @@ -335,7 +359,7 @@ impl LeanIxonConstantMeta { /// | recr | 6 | 7 (name, lvls, rules, all, ctx, arena, ruleRoots) | 8 (1× u64) | pub fn build(meta: &ConstantMeta) -> Self { let obj = match meta { - ConstantMeta::Empty => LeanObject::box_usize(0), + ConstantMeta::Empty => LeanOwned::box_usize(0), ConstantMeta::Def { name, @@ -354,9 +378,9 @@ impl LeanIxonConstantMeta { ctor.set(3, LeanIxAddress::build_array(all)); ctor.set(4, LeanIxAddress::build_array(ctx)); ctor.set(5, LeanIxonExprMetaArena::build(arena)); - ctor.set_scalar_u64(6, 0, *type_root); - ctor.set_scalar_u64(6, 8, *value_root); - *ctor + ctor.set_u64(6, 0, *type_root); + ctor.set_u64(6, 8, *value_root); + ctor.into() }, ConstantMeta::Axio { name, lvls, arena, type_root } => { @@ -364,8 +388,8 @@ impl LeanIxonConstantMeta { ctor.set(0, LeanIxAddress::build(name)); ctor.set(1, LeanIxAddress::build_array(lvls)); ctor.set(2, LeanIxonExprMetaArena::build(arena)); - ctor.set_scalar_u64(3, 0, *type_root); - *ctor + ctor.set_u64(3, 0, *type_root); + ctor.into() }, ConstantMeta::Quot { name, lvls, arena, type_root } => { @@ -373,8 +397,8 @@ impl LeanIxonConstantMeta { ctor.set(0, LeanIxAddress::build(name)); ctor.set(1, LeanIxAddress::build_array(lvls)); ctor.set(2, LeanIxonExprMetaArena::build(arena)); - ctor.set_scalar_u64(3, 0, *type_root); - *ctor + ctor.set_u64(3, 0, *type_root); + ctor.into() }, ConstantMeta::Indc { name, lvls, ctors, all, ctx, arena, type_root } => { @@ -385,8 +409,8 @@ impl LeanIxonConstantMeta { ctor.set(3, LeanIxAddress::build_array(all)); ctor.set(4, LeanIxAddress::build_array(ctx)); ctor.set(5, LeanIxonExprMetaArena::build(arena)); - ctor.set_scalar_u64(6, 0, *type_root); - *ctor + ctor.set_u64(6, 0, *type_root); + ctor.into() }, ConstantMeta::Ctor { name, lvls, induct, arena, type_root } => { @@ -395,8 +419,8 @@ impl LeanIxonConstantMeta { ctor.set(1, LeanIxAddress::build_array(lvls)); ctor.set(2, LeanIxAddress::build(induct)); ctor.set(3, LeanIxonExprMetaArena::build(arena)); - ctor.set_scalar_u64(4, 0, *type_root); - *ctor + ctor.set_u64(4, 0, *type_root); + ctor.into() }, ConstantMeta::Rec { @@ -417,18 +441,20 @@ impl LeanIxonConstantMeta { ctor.set(4, LeanIxAddress::build_array(ctx)); ctor.set(5, LeanIxonExprMetaArena::build(arena)); ctor.set(6, build_u64_array(rule_roots)); - ctor.set_scalar_u64(7, 0, *type_root); - *ctor + ctor.set_u64(7, 0, *type_root); + ctor.into() }, }; Self::new(obj) } +} +impl LeanIxonConstantMeta { /// Decode Ixon.ConstantMeta from Lean pointer. - pub fn decode(self) -> ConstantMeta { + pub fn decode(&self) -> ConstantMeta { // Empty (tag 0, no fields) is represented as a scalar lean_box(0) - if self.is_scalar() { - let tag = self.as_ptr() as usize >> 1; + if self.inner().is_scalar() { + let tag = self.inner().as_raw() as usize >> 1; assert_eq!(tag, 0, "Invalid scalar ConstantMeta tag: {}", tag); return ConstantMeta::Empty; } @@ -436,14 +462,17 @@ impl LeanIxonConstantMeta { match ctor.tag() { 1 => { // defn: 6 obj fields, 2× u64 scalar - let name = LeanIxAddress::new(ctor.get(0)).decode(); + let name = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); let lvls = decode_address_array(ctor.get(1).as_array()); - let hints = LeanIxReducibilityHints::new(ctor.get(2)).decode(); + let hints = + LeanIxReducibilityHints::new(ctor.get(2).to_owned_ref()).decode(); let all = decode_address_array(ctor.get(3).as_array()); let ctx = decode_address_array(ctor.get(4).as_array()); - let arena = LeanIxonExprMetaArena::new(ctor.get(5)).decode(); - let type_root = ctor.scalar_u64(6, 0); - let value_root = ctor.scalar_u64(6, 8); + let arena = + LeanIxonExprMetaArena::new(ctor.get(5).to_owned_ref()).decode(); + let type_root = ctor.get_u64(6, 0); + let value_root = ctor.get_u64(6, 8); ConstantMeta::Def { name, lvls, @@ -458,54 +487,65 @@ impl LeanIxonConstantMeta { 2 => { // axio: 3 obj fields, 1× u64 scalar - let name = LeanIxAddress::new(ctor.get(0)).decode(); + let name = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); let lvls = decode_address_array(ctor.get(1).as_array()); - let arena = LeanIxonExprMetaArena::new(ctor.get(2)).decode(); - let type_root = ctor.scalar_u64(3, 0); + let arena = + LeanIxonExprMetaArena::new(ctor.get(2).to_owned_ref()).decode(); + let type_root = ctor.get_u64(3, 0); ConstantMeta::Axio { name, lvls, arena, type_root } }, 3 => { // quot: 3 obj fields, 1× u64 scalar - let name = LeanIxAddress::new(ctor.get(0)).decode(); + let name = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); let lvls = decode_address_array(ctor.get(1).as_array()); - let arena = LeanIxonExprMetaArena::new(ctor.get(2)).decode(); - let type_root = ctor.scalar_u64(3, 0); + let arena = + LeanIxonExprMetaArena::new(ctor.get(2).to_owned_ref()).decode(); + let type_root = ctor.get_u64(3, 0); ConstantMeta::Quot { name, lvls, arena, type_root } }, 4 => { // indc: 6 obj fields, 1× u64 scalar - let name = LeanIxAddress::new(ctor.get(0)).decode(); + let name = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); let lvls = decode_address_array(ctor.get(1).as_array()); let ctors = decode_address_array(ctor.get(2).as_array()); let all = decode_address_array(ctor.get(3).as_array()); let ctx = decode_address_array(ctor.get(4).as_array()); - let arena = LeanIxonExprMetaArena::new(ctor.get(5)).decode(); - let type_root = ctor.scalar_u64(6, 0); + let arena = + LeanIxonExprMetaArena::new(ctor.get(5).to_owned_ref()).decode(); + let type_root = ctor.get_u64(6, 0); ConstantMeta::Indc { name, lvls, ctors, all, ctx, arena, type_root } }, 5 => { // ctor: 4 obj fields, 1× u64 scalar - let name = LeanIxAddress::new(ctor.get(0)).decode(); + let name = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); let lvls = decode_address_array(ctor.get(1).as_array()); - let induct = LeanIxAddress::new(ctor.get(2)).decode(); - let arena = LeanIxonExprMetaArena::new(ctor.get(3)).decode(); - let type_root = ctor.scalar_u64(4, 0); + let induct = + LeanIxAddress::from_borrowed(ctor.get(2).as_byte_array()).decode(); + let arena = + LeanIxonExprMetaArena::new(ctor.get(3).to_owned_ref()).decode(); + let type_root = ctor.get_u64(4, 0); ConstantMeta::Ctor { name, lvls, induct, arena, type_root } }, 6 => { // recr: 7 obj fields, 1× u64 scalar - let name = LeanIxAddress::new(ctor.get(0)).decode(); + let name = + LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(); let lvls = decode_address_array(ctor.get(1).as_array()); let rules = decode_address_array(ctor.get(2).as_array()); let all = decode_address_array(ctor.get(3).as_array()); let ctx = decode_address_array(ctor.get(4).as_array()); - let arena = LeanIxonExprMetaArena::new(ctor.get(5)).decode(); + let arena = + LeanIxonExprMetaArena::new(ctor.get(5).to_owned_ref()).decode(); let rule_roots = decode_u64_array(ctor.get(6).as_array()); - let type_root = ctor.scalar_u64(7, 0); + let type_root = ctor.get_u64(7, 0); ConstantMeta::Rec { name, lvls, @@ -527,7 +567,7 @@ impl LeanIxonConstantMeta { // Named and Comm Build/Decode // ============================================================================= -impl LeanIxonNamed { +impl LeanIxonNamed { /// Build Ixon.Named { addr : Address, constMeta : ConstantMeta } pub fn build(addr: &Address, meta: &ConstantMeta) -> Self { let addr_obj = LeanIxAddress::build(addr); @@ -535,20 +575,22 @@ impl LeanIxonNamed { let ctor = LeanCtor::alloc(0, 2, 0); ctor.set(0, addr_obj); ctor.set(1, meta_obj); - Self::new(*ctor) + Self::new(ctor.into()) } +} +impl LeanIxonNamed { /// Decode Ixon.Named. - pub fn decode(self) -> Named { + pub fn decode(&self) -> Named { let ctor = self.as_ctor(); Named { - addr: LeanIxAddress::new(ctor.get(0)).decode(), - meta: LeanIxonConstantMeta::new(ctor.get(1)).decode(), + addr: LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()).decode(), + meta: LeanIxonConstantMeta::new(ctor.get(1).to_owned_ref()).decode(), } } } -impl LeanIxonComm { +impl LeanIxonComm { /// Build Ixon.Comm { secret : Address, payload : Address } pub fn build(comm: &Comm) -> Self { let secret_obj = LeanIxAddress::build(&comm.secret); @@ -556,15 +598,19 @@ impl LeanIxonComm { let ctor = LeanCtor::alloc(0, 2, 0); ctor.set(0, secret_obj); ctor.set(1, payload_obj); - Self::new(*ctor) + Self::new(ctor.into()) } +} +impl LeanIxonComm { /// Decode Ixon.Comm. - pub fn decode(self) -> Comm { + pub fn decode(&self) -> Comm { let ctor = self.as_ctor(); Comm { - secret: LeanIxAddress::new(ctor.get(0)).decode(), - payload: LeanIxAddress::new(ctor.get(1)).decode(), + secret: LeanIxAddress::from_borrowed(ctor.get(0).as_byte_array()) + .decode(), + payload: LeanIxAddress::from_borrowed(ctor.get(1).as_byte_array()) + .decode(), } } } @@ -574,51 +620,61 @@ impl LeanIxonComm { // ============================================================================= /// Round-trip Ixon.DataValue. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_data_value( - obj: LeanIxonDataValue, -) -> LeanIxonDataValue { + obj: LeanIxonDataValue>, +) -> LeanIxonDataValue { let dv = obj.decode(); LeanIxonDataValue::build(&dv) } /// Round-trip Ixon.Comm. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ixon_comm(obj: LeanIxonComm) -> LeanIxonComm { +pub extern "C" fn rs_roundtrip_ixon_comm( + obj: LeanIxonComm>, +) -> LeanIxonComm { let comm = obj.decode(); LeanIxonComm::build(&comm) } /// Round-trip Ixon.ExprMetaData. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_expr_meta_data( - obj: LeanIxonExprMetaData, -) -> LeanIxonExprMetaData { + obj: LeanIxonExprMetaData>, +) -> LeanIxonExprMetaData { let node = obj.decode(); LeanIxonExprMetaData::build(&node) } /// Round-trip Ixon.ExprMetaArena. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_expr_meta_arena( - obj: LeanIxonExprMetaArena, -) -> LeanIxonExprMetaArena { + obj: LeanIxonExprMetaArena>, +) -> LeanIxonExprMetaArena { let arena = obj.decode(); LeanIxonExprMetaArena::build(&arena) } /// Round-trip Ixon.ConstantMeta (full arena-based). +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_ixon_constant_meta( - obj: LeanIxonConstantMeta, -) -> LeanIxonConstantMeta { + obj: LeanIxonConstantMeta>, +) -> LeanIxonConstantMeta { let meta = obj.decode(); LeanIxonConstantMeta::build(&meta) } /// Round-trip Ixon.Named (with real metadata). +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ixon_named(obj: LeanIxonNamed) -> LeanIxonNamed { +pub extern "C" fn rs_roundtrip_ixon_named( + obj: LeanIxonNamed>, +) -> LeanIxonNamed { let named = obj.decode(); LeanIxonNamed::build(&named.addr, &named.meta) } diff --git a/src/ffi/ixon/serialize.rs b/src/ffi/ixon/serialize.rs index 14a3683c..ab09a94a 100644 --- a/src/ffi/ixon/serialize.rs +++ b/src/ffi/ixon/serialize.rs @@ -12,13 +12,13 @@ use crate::ix::ixon::univ::put_univ; use crate::lean::{ LeanIxAddress, LeanIxonConstant, LeanIxonExpr, LeanIxonRawEnv, LeanIxonUniv, }; -use lean_ffi::object::LeanByteArray; +use lean_ffi::object::{LeanBorrowed, LeanByteArray, LeanOwned}; /// Check if Lean's computed hash matches Rust's computed hash. #[unsafe(no_mangle)] pub extern "C" fn rs_expr_hash_matches( - expr_obj: LeanIxonExpr, - expected_hash: LeanIxAddress, + expr_obj: LeanIxonExpr>, + expected_hash: LeanIxAddress>, ) -> bool { let expr = Arc::new(expr_obj.decode()); let hash = hash_expr(&expr); @@ -29,8 +29,8 @@ pub extern "C" fn rs_expr_hash_matches( /// Check if Lean's Ixon.Univ serialization matches Rust. #[unsafe(no_mangle)] pub extern "C" fn rs_eq_univ_serialization( - univ_obj: LeanIxonUniv, - bytes_obj: LeanByteArray, + univ_obj: LeanIxonUniv>, + bytes_obj: LeanByteArray>, ) -> bool { let univ = univ_obj.decode(); let bytes_data = bytes_obj.as_bytes(); @@ -42,8 +42,8 @@ pub extern "C" fn rs_eq_univ_serialization( /// Check if Lean's Ixon.Expr serialization matches Rust. #[unsafe(no_mangle)] pub extern "C" fn rs_eq_expr_serialization( - expr_obj: LeanIxonExpr, - bytes_obj: LeanByteArray, + expr_obj: LeanIxonExpr>, + bytes_obj: LeanByteArray>, ) -> bool { let expr = expr_obj.decode(); let bytes_data = bytes_obj.as_bytes(); @@ -55,8 +55,8 @@ pub extern "C" fn rs_eq_expr_serialization( /// Check if Lean's Ixon.Constant serialization matches Rust. #[unsafe(no_mangle)] pub extern "C" fn rs_eq_constant_serialization( - constant_obj: LeanIxonConstant, - bytes_obj: LeanByteArray, + constant_obj: LeanIxonConstant>, + bytes_obj: LeanByteArray>, ) -> bool { let constant = constant_obj.decode(); let bytes_data = bytes_obj.as_bytes(); @@ -69,8 +69,8 @@ pub extern "C" fn rs_eq_constant_serialization( /// Due to HashMap ordering differences, we compare deserialized content rather than bytes. #[unsafe(no_mangle)] pub extern "C" fn rs_eq_env_serialization( - raw_env_obj: LeanIxonRawEnv, - bytes_obj: LeanByteArray, + raw_env_obj: LeanIxonRawEnv>, + bytes_obj: LeanByteArray>, ) -> bool { use crate::ix::ixon::env::Env; @@ -137,7 +137,9 @@ pub extern "C" fn rs_eq_env_serialization( /// /// Returns: true if Rust can deserialize and re-serialize to the same bytes #[unsafe(no_mangle)] -extern "C" fn rs_env_serde_roundtrip(lean_bytes_obj: LeanByteArray) -> bool { +extern "C" fn rs_env_serde_roundtrip( + lean_bytes_obj: LeanByteArray, +) -> bool { use crate::ix::ixon::env::Env; // Get bytes from Lean ByteArray @@ -183,7 +185,9 @@ extern "C" fn rs_env_serde_roundtrip(lean_bytes_obj: LeanByteArray) -> bool { /// /// Returns: true if Rust can deserialize and the counts match #[unsafe(no_mangle)] -extern "C" fn rs_env_serde_check(lean_bytes_obj: LeanByteArray) -> bool { +extern "C" fn rs_env_serde_check( + lean_bytes_obj: LeanByteArray, +) -> bool { use crate::ix::ixon::env::Env; // Get bytes from Lean ByteArray diff --git a/src/ffi/ixon/sharing.rs b/src/ffi/ixon/sharing.rs index 85e5ddd9..6fffeb0c 100644 --- a/src/ffi/ixon/sharing.rs +++ b/src/ffi/ixon/sharing.rs @@ -8,14 +8,16 @@ use crate::ix::ixon::sharing::{ analyze_block, build_sharing_vec, decide_sharing, }; use crate::lean::LeanIxonExpr; -use lean_ffi::object::{LeanArray, LeanByteArray}; +use lean_ffi::object::{LeanArray, LeanBorrowed, LeanByteArray, LeanOwned}; /// FFI: Debug sharing analysis - print usage counts for subterms with usage >= 2. /// This helps diagnose why Lean and Rust make different sharing decisions. #[unsafe(no_mangle)] -pub extern "C" fn rs_debug_sharing_analysis(exprs_obj: LeanArray) { +pub extern "C" fn rs_debug_sharing_analysis( + exprs_obj: LeanArray>, +) { let exprs: Vec> = - exprs_obj.map(|x| Arc::new(LeanIxonExpr::new(x).decode())); + exprs_obj.map(|x| Arc::new(LeanIxonExpr(x).decode())); println!("[Rust] Analyzing {} input expressions", exprs.len()); @@ -55,8 +57,10 @@ pub extern "C" fn rs_debug_sharing_analysis(exprs_obj: LeanArray) { /// FFI: Run Rust's sharing analysis on Lean-provided Ixon.Expr array. /// Returns the number of shared items Rust would produce. #[unsafe(no_mangle)] -extern "C" fn rs_analyze_sharing_count(exprs_obj: LeanArray) -> u64 { - let exprs = LeanIxonExpr::decode_array(exprs_obj); +extern "C" fn rs_analyze_sharing_count( + exprs_obj: LeanArray>, +) -> u64 { + let exprs = LeanIxonExpr::decode_array(&exprs_obj); let (info_map, _ptr_to_hash) = analyze_block(&exprs, false); let shared_hashes = decide_sharing(&info_map); @@ -69,11 +73,11 @@ extern "C" fn rs_analyze_sharing_count(exprs_obj: LeanArray) -> u64 { /// Returns number of shared items. #[unsafe(no_mangle)] extern "C" fn rs_run_sharing_analysis( - exprs_obj: LeanArray, - out_sharing_vec: LeanByteArray, - out_rewritten: LeanByteArray, + exprs_obj: LeanArray>, + out_sharing_vec: LeanByteArray, + out_rewritten: LeanByteArray, ) -> u64 { - let exprs = LeanIxonExpr::decode_array(exprs_obj); + let exprs = LeanIxonExpr::decode_array(&exprs_obj); let (info_map, ptr_to_hash) = analyze_block(&exprs, false); let shared_hashes = decide_sharing(&info_map); @@ -107,15 +111,15 @@ extern "C" fn rs_run_sharing_analysis( /// - bits 48-63: Rust sharing count #[unsafe(no_mangle)] extern "C" fn rs_compare_sharing_analysis( - exprs_obj: LeanArray, - lean_sharing_obj: LeanArray, - _lean_rewritten_obj: LeanArray, + exprs_obj: LeanArray>, + lean_sharing_obj: LeanArray>, + _lean_rewritten_obj: LeanArray>, ) -> u64 { // Decode input expressions - let exprs = LeanIxonExpr::decode_array(exprs_obj); + let exprs = LeanIxonExpr::decode_array(&exprs_obj); // Decode Lean's sharing vector - let lean_sharing = LeanIxonExpr::decode_array(lean_sharing_obj); + let lean_sharing = LeanIxonExpr::decode_array(&lean_sharing_obj); // Run Rust's sharing analysis let (info_map, ptr_to_hash) = analyze_block(&exprs, false); diff --git a/src/ffi/ixon/univ.rs b/src/ffi/ixon/univ.rs index 7bf427ee..93748535 100644 --- a/src/ffi/ixon/univ.rs +++ b/src/ffi/ixon/univ.rs @@ -4,74 +4,77 @@ use std::sync::Arc; use crate::ix::ixon::univ::Univ; use crate::lean::LeanIxonUniv; -use lean_ffi::object::{LeanArray, LeanCtor, LeanObject}; +use lean_ffi::object::{LeanArray, LeanBorrowed, LeanCtor, LeanOwned, LeanRef}; -impl LeanIxonUniv { +impl LeanIxonUniv { /// Build Ixon.Univ pub fn build(univ: &Univ) -> Self { let obj = match univ { - Univ::Zero => LeanObject::box_usize(0), + Univ::Zero => LeanOwned::box_usize(0), Univ::Succ(inner) => { let ctor = LeanCtor::alloc(1, 1, 0); ctor.set(0, Self::build(inner)); - *ctor + ctor.into() }, Univ::Max(a, b) => { let ctor = LeanCtor::alloc(2, 2, 0); ctor.set(0, Self::build(a)); ctor.set(1, Self::build(b)); - *ctor + ctor.into() }, Univ::IMax(a, b) => { let ctor = LeanCtor::alloc(3, 2, 0); ctor.set(0, Self::build(a)); ctor.set(1, Self::build(b)); - *ctor + ctor.into() }, Univ::Var(idx) => { let ctor = LeanCtor::alloc(4, 0, 8); - ctor.set_scalar_u64(0, 0, *idx); - *ctor + ctor.set_u64(0, 0, *idx); + ctor.into() }, }; Self::new(obj) } /// Build an Array of Ixon.Univ. - pub fn build_array(univs: &[Arc]) -> LeanArray { + pub fn build_array(univs: &[Arc]) -> LeanArray { let arr = LeanArray::alloc(univs.len()); for (i, univ) in univs.iter().enumerate() { arr.set(i, Self::build(univ)); } arr } +} +impl LeanIxonUniv { /// Decode Ixon.Univ (recursive enum). - pub fn decode(self) -> Univ { - let obj: LeanObject = *self; - if obj.is_scalar() { + pub fn decode(&self) -> Univ { + if self.inner().is_scalar() { return Univ::Zero; } - let ctor = obj.as_ctor(); + let ctor = self.as_ctor(); match ctor.tag() { 0 => Univ::Zero, - 1 => Univ::Succ(Arc::new(Self::new(ctor.get(0)).decode())), + 1 => Univ::Succ(Arc::new(LeanIxonUniv(ctor.get(0)).decode())), 2 => Univ::Max( - Arc::new(Self::new(ctor.get(0)).decode()), - Arc::new(Self::new(ctor.get(1)).decode()), + Arc::new(LeanIxonUniv(ctor.get(0)).decode()), + Arc::new(LeanIxonUniv(ctor.get(1)).decode()), ), 3 => Univ::IMax( - Arc::new(Self::new(ctor.get(0)).decode()), - Arc::new(Self::new(ctor.get(1)).decode()), + Arc::new(LeanIxonUniv(ctor.get(0)).decode()), + Arc::new(LeanIxonUniv(ctor.get(1)).decode()), ), - 4 => Univ::Var(ctor.scalar_u64(0, 0)), + 4 => Univ::Var(ctor.get_u64(0, 0)), tag => panic!("Invalid Ixon.Univ tag: {tag}"), } } +} +impl LeanIxonUniv> { /// Decode Array Ixon.Univ. - pub fn decode_array(obj: LeanArray) -> Vec> { - obj.map(|elem| Arc::new(Self::new(elem).decode())) + pub fn decode_array(obj: &LeanArray>) -> Vec> { + obj.map(|elem| Arc::new(LeanIxonUniv(elem).decode())) } } @@ -80,8 +83,11 @@ impl LeanIxonUniv { // ============================================================================= /// Round-trip Ixon.Univ. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -pub extern "C" fn rs_roundtrip_ixon_univ(obj: LeanIxonUniv) -> LeanIxonUniv { +pub extern "C" fn rs_roundtrip_ixon_univ( + obj: LeanIxonUniv>, +) -> LeanIxonUniv { let univ = obj.decode(); LeanIxonUniv::build(&univ) } diff --git a/src/ffi/keccak.rs b/src/ffi/keccak.rs index 3638a3d9..c3127bfb 100644 --- a/src/ffi/keccak.rs +++ b/src/ffi/keccak.rs @@ -3,7 +3,7 @@ use std::sync::OnceLock; use tiny_keccak::{Hasher, Keccak}; use lean_ffi::object::{ - ExternalClass, LeanByteArray, LeanExternal, LeanObject, + ExternalClass, LeanBorrowed, LeanByteArray, LeanExternal, LeanOwned, }; static KECCAK_CLASS: OnceLock = OnceLock::new(); @@ -15,17 +15,17 @@ fn keccak_class() -> &'static ExternalClass { /// `Keccak.Hasher.init : Unit → Hasher` #[unsafe(no_mangle)] extern "C" fn rs_keccak256_hasher_init( - _unit: LeanObject, -) -> LeanExternal { + _unit: LeanOwned, +) -> LeanExternal { LeanExternal::alloc(keccak_class(), Keccak::v256()) } /// `Keccak.Hasher.update : (hasher: Hasher) → (input: @& ByteArray) → Hasher` #[unsafe(no_mangle)] extern "C" fn rs_keccak256_hasher_update( - hasher: LeanExternal, - input: LeanByteArray, -) -> LeanExternal { + hasher: LeanExternal, + input: LeanByteArray>, +) -> LeanExternal { let mut new_hasher = hasher.get().clone(); new_hasher.update(input.as_bytes()); LeanExternal::alloc(keccak_class(), new_hasher) @@ -34,8 +34,8 @@ extern "C" fn rs_keccak256_hasher_update( /// `Keccak.Hasher.finalize : (hasher: Hasher) → ByteArray` #[unsafe(no_mangle)] extern "C" fn rs_keccak256_hasher_finalize( - hasher: LeanExternal, -) -> LeanByteArray { + hasher: LeanExternal, +) -> LeanByteArray { let mut data = [0u8; 32]; hasher.get().clone().finalize(&mut data); LeanByteArray::from_bytes(&data) diff --git a/src/ffi/lean_env.rs b/src/ffi/lean_env.rs index 695b29c7..b373838d 100644 --- a/src/ffi/lean_env.rs +++ b/src/ffi/lean_env.rs @@ -14,47 +14,33 @@ use dashmap::DashMap; use rayon::prelude::*; -use std::ffi::c_void; -use std::sync::Arc; use rustc_hash::FxHashMap; +#[cfg(feature = "test-ffi")] +use std::sync::Arc; +#[cfg(feature = "test-ffi")] +use crate::ix::compile::compile_env; +#[cfg(feature = "test-ffi")] +use crate::ix::decompile::{check_decompile, decompile_env}; + use lean_ffi::nat::Nat; -use lean_ffi::object::{LeanList, LeanObject}; - -use crate::{ - ix::compile::compile_env, - ix::decompile::{check_decompile, decompile_env}, - ix::env::{ - AxiomVal, BinderInfo, ConstantInfo, ConstantVal, ConstructorVal, DataValue, - DefinitionSafety, DefinitionVal, Env, Expr, InductiveVal, Int, Level, - Literal, Name, OpaqueVal, QuotKind, QuotVal, RecursorRule, RecursorVal, - ReducibilityHints, SourceInfo, Substring, Syntax, SyntaxPreresolved, - TheoremVal, - }, +use lean_ffi::object::{LeanBorrowed, LeanList, LeanRef, LeanShared}; + +use crate::ix::env::{ + AxiomVal, BinderInfo, ConstantInfo, ConstantVal, ConstructorVal, DataValue, + DefinitionSafety, DefinitionVal, Env, Expr, InductiveVal, Int, Level, + Literal, Name, OpaqueVal, QuotKind, QuotVal, RecursorRule, RecursorVal, + ReducibilityHints, SourceInfo, Substring, Syntax, SyntaxPreresolved, + TheoremVal, }; const PARALLEL_THRESHOLD: usize = 100; -/// Wrapper to allow sending `LeanObject` across threads. The underlying Lean -/// objects must remain valid for the entire duration of parallel decoding. -#[derive(Clone, Copy)] -struct SendObj(LeanObject); - -unsafe impl Send for SendObj {} -unsafe impl Sync for SendObj {} - -impl SendObj { - #[inline] - fn get(self) -> LeanObject { - self.0 - } -} - /// Global cache for Names, shared across all threads. #[derive(Default)] pub struct GlobalCache { - names: DashMap<*const c_void, Name>, + names: DashMap<*mut lean_ffi::include::lean_object, Name>, } impl GlobalCache { @@ -75,8 +61,8 @@ unsafe impl Sync for GlobalCache {} /// Thread-local cache for Levels and Exprs. #[derive(Default)] struct LocalCache { - univs: FxHashMap<*const c_void, Level>, - exprs: FxHashMap<*const c_void, Expr>, + univs: FxHashMap<*mut lean_ffi::include::lean_object, Level>, + exprs: FxHashMap<*mut lean_ffi::include::lean_object, Expr>, } // SAFETY: LocalCache is only accessed by a single thread. @@ -94,13 +80,13 @@ impl<'g> Cache<'g> { } } -fn collect_list_objs(list: LeanList) -> Vec { - list.iter().collect() +fn collect_list_objs(list: LeanList>) -> Vec { + list.iter().map(|b| LeanShared::new(b.to_owned_ref())).collect() } // Name decoding with global cache -pub fn decode_name(obj: LeanObject, global: &GlobalCache) -> Name { - let ptr = obj.as_ptr(); +pub fn decode_name(obj: LeanBorrowed<'_>, global: &GlobalCache) -> Name { + let ptr = obj.as_raw(); // Fast path: check if already cached if let Some(name) = global.names.get(&ptr) { return name.clone(); @@ -116,7 +102,7 @@ pub fn decode_name(obj: LeanObject, global: &GlobalCache) -> Name { let pre = decode_name(pre, global); match ctor.tag() { 1 => Name::str(pre, pos.as_string().to_string()), - 2 => Name::num(pre, Nat::from_obj(pos)), + 2 => Name::num(pre, Nat::from_obj(&pos)), _ => unreachable!(), } }; @@ -125,8 +111,8 @@ pub fn decode_name(obj: LeanObject, global: &GlobalCache) -> Name { global.names.entry(ptr).or_insert(name).clone() } -fn decode_level(obj: LeanObject, cache: &mut Cache<'_>) -> Level { - let ptr = obj.as_ptr(); +fn decode_level(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Level { + let ptr = obj.as_raw(); if let Some(cached) = cache.local.univs.get(&ptr) { return cached.clone(); } @@ -162,16 +148,16 @@ fn decode_level(obj: LeanObject, cache: &mut Cache<'_>) -> Level { level } -fn decode_substring(obj: LeanObject) -> Substring { +fn decode_substring(obj: LeanBorrowed<'_>) -> Substring { let ctor = obj.as_ctor(); let [str_obj, start_pos, stop_pos] = ctor.objs(); let str = str_obj.as_string().to_string(); - let start_pos = Nat::from_obj(start_pos); - let stop_pos = Nat::from_obj(stop_pos); + let start_pos = Nat::from_obj(&start_pos); + let stop_pos = Nat::from_obj(&stop_pos); Substring { str, start_pos, stop_pos } } -fn decode_source_info(obj: LeanObject) -> SourceInfo { +fn decode_source_info(obj: LeanBorrowed<'_>) -> SourceInfo { if obj.is_scalar() { return SourceInfo::None; } @@ -180,16 +166,16 @@ fn decode_source_info(obj: LeanObject) -> SourceInfo { 0 => { let [leading, pos, trailing, end_pos] = ctor.objs(); let leading = decode_substring(leading); - let pos = Nat::from_obj(pos); + let pos = Nat::from_obj(&pos); let trailing = decode_substring(trailing); - let end_pos = Nat::from_obj(end_pos); + let end_pos = Nat::from_obj(&end_pos); SourceInfo::Original(leading, pos, trailing, end_pos) }, 1 => { let [pos, end_pos, canonical] = ctor.objs(); - let pos = Nat::from_obj(pos); - let end_pos = Nat::from_obj(end_pos); - let canonical = canonical.as_ptr() as usize == 1; + let pos = Nat::from_obj(&pos); + let end_pos = Nat::from_obj(&end_pos); + let canonical = canonical.as_raw() as usize == 1; SourceInfo::Synthetic(pos, end_pos, canonical) }, _ => unreachable!(), @@ -197,7 +183,7 @@ fn decode_source_info(obj: LeanObject) -> SourceInfo { } fn decode_syntax_preresolved( - obj: LeanObject, + obj: LeanBorrowed<'_>, cache: &mut Cache<'_>, ) -> SyntaxPreresolved { let ctor = obj.as_ctor(); @@ -221,7 +207,7 @@ fn decode_syntax_preresolved( } } -fn decode_syntax(obj: LeanObject, cache: &mut Cache<'_>) -> Syntax { +fn decode_syntax(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Syntax { if obj.is_scalar() { return Syntax::Missing; } @@ -247,7 +233,7 @@ fn decode_syntax(obj: LeanObject, cache: &mut Cache<'_>) -> Syntax { let val = decode_name(val, cache.global); let preresolved = collect_list_objs(preresolved.as_list()) .into_iter() - .map(|o| decode_syntax_preresolved(o, cache)) + .map(|o| decode_syntax_preresolved(o.borrow(), cache)) .collect(); Syntax::Ident(info, raw_val, val, preresolved) }, @@ -256,7 +242,7 @@ fn decode_syntax(obj: LeanObject, cache: &mut Cache<'_>) -> Syntax { } fn decode_name_data_value( - obj: LeanObject, + obj: LeanBorrowed<'_>, cache: &mut Cache<'_>, ) -> (Name, DataValue) { let ctor = obj.as_ctor(); @@ -266,13 +252,13 @@ fn decode_name_data_value( let [inner] = dv_ctor.objs::<1>(); let data_value = match dv_ctor.tag() { 0 => DataValue::OfString(inner.as_string().to_string()), - 1 => DataValue::OfBool(inner.as_ptr() as usize == 1), + 1 => DataValue::OfBool(inner.as_raw() as usize == 1), 2 => DataValue::OfName(decode_name(inner, cache.global)), - 3 => DataValue::OfNat(Nat::from_obj(inner)), + 3 => DataValue::OfNat(Nat::from_obj(&inner)), 4 => { let inner_ctor = inner.as_ctor(); let [nat_obj] = inner_ctor.objs::<1>(); - let nat = Nat::from_obj(nat_obj); + let nat = Nat::from_obj(&nat_obj); let int = match inner_ctor.tag() { 0 => Int::OfNat(nat), 1 => Int::NegSucc(nat), @@ -286,8 +272,8 @@ fn decode_name_data_value( (name, data_value) } -pub fn decode_expr(obj: LeanObject, cache: &mut Cache<'_>) -> Expr { - let ptr = obj.as_ptr(); +pub fn decode_expr(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Expr { + let ptr = obj.as_raw(); if let Some(cached) = cache.local.exprs.get(&ptr) { return cached.clone(); } @@ -295,7 +281,7 @@ pub fn decode_expr(obj: LeanObject, cache: &mut Cache<'_>) -> Expr { let expr = match ctor.tag() { 0 => { let [nat, _hash] = ctor.objs(); - Expr::bvar(Nat::from_obj(nat)) + Expr::bvar(Nat::from_obj(&nat)) }, 1 => { let [name_obj, _hash] = ctor.objs(); @@ -317,7 +303,7 @@ pub fn decode_expr(obj: LeanObject, cache: &mut Cache<'_>) -> Expr { let name = decode_name(name_obj, cache.global); let levels = collect_list_objs(levels.as_list()) .into_iter() - .map(|o| decode_level(o, cache)) + .map(|o| decode_level(o.borrow(), cache)) .collect(); Expr::cnst(name, levels) }, @@ -332,7 +318,7 @@ pub fn decode_expr(obj: LeanObject, cache: &mut Cache<'_>) -> Expr { let binder_name = decode_name(binder_name, cache.global); let binder_typ = decode_expr(binder_typ, cache); let body = decode_expr(body, cache); - let binder_info = match binder_info.as_ptr() as usize { + let binder_info = match binder_info.as_raw() as usize { 0 => BinderInfo::Default, 1 => BinderInfo::Implicit, 2 => BinderInfo::StrictImplicit, @@ -346,7 +332,7 @@ pub fn decode_expr(obj: LeanObject, cache: &mut Cache<'_>) -> Expr { let binder_name = decode_name(binder_name, cache.global); let binder_typ = decode_expr(binder_typ, cache); let body = decode_expr(body, cache); - let binder_info = match binder_info.as_ptr() as usize { + let binder_info = match binder_info.as_raw() as usize { 0 => BinderInfo::Default, 1 => BinderInfo::Implicit, 2 => BinderInfo::StrictImplicit, @@ -361,7 +347,7 @@ pub fn decode_expr(obj: LeanObject, cache: &mut Cache<'_>) -> Expr { let typ = decode_expr(typ, cache); let value = decode_expr(value, cache); let body = decode_expr(body, cache); - let nondep = nondep.as_ptr() as usize == 1; + let nondep = nondep.as_raw() as usize == 1; Expr::letE(decl_name, typ, value, body, nondep) }, 9 => { @@ -369,7 +355,7 @@ pub fn decode_expr(obj: LeanObject, cache: &mut Cache<'_>) -> Expr { let lit_ctor = literal.as_ctor(); let [inner] = lit_ctor.objs::<1>(); match lit_ctor.tag() { - 0 => Expr::lit(Literal::NatVal(Nat::from_obj(inner))), + 0 => Expr::lit(Literal::NatVal(Nat::from_obj(&inner))), 1 => Expr::lit(Literal::StrVal(inner.as_string().to_string())), _ => unreachable!(), } @@ -378,7 +364,7 @@ pub fn decode_expr(obj: LeanObject, cache: &mut Cache<'_>) -> Expr { let [data, expr_obj] = ctor.objs(); let kv_map: Vec<_> = collect_list_objs(data.as_list()) .into_iter() - .map(|o| decode_name_data_value(o, cache)) + .map(|o| decode_name_data_value(o.borrow(), cache)) .collect(); let expr = decode_expr(expr_obj, cache); Expr::mdata(kv_map, expr) @@ -386,7 +372,7 @@ pub fn decode_expr(obj: LeanObject, cache: &mut Cache<'_>) -> Expr { 11 => { let [typ_name, idx, struct_expr] = ctor.objs(); let typ_name = decode_name(typ_name, cache.global); - let idx = Nat::from_obj(idx); + let idx = Nat::from_obj(&idx); let struct_expr = decode_expr(struct_expr, cache); Expr::proj(typ_name, idx, struct_expr) }, @@ -397,31 +383,34 @@ pub fn decode_expr(obj: LeanObject, cache: &mut Cache<'_>) -> Expr { } fn decode_recursor_rule( - obj: LeanObject, + obj: LeanBorrowed<'_>, cache: &mut Cache<'_>, ) -> RecursorRule { let ctor = obj.as_ctor(); let [ctor_name, n_fields, rhs] = ctor.objs(); let ctor_name = decode_name(ctor_name, cache.global); - let n_fields = Nat::from_obj(n_fields); + let n_fields = Nat::from_obj(&n_fields); let rhs = decode_expr(rhs, cache); RecursorRule { ctor: ctor_name, n_fields, rhs } } -fn decode_constant_val(obj: LeanObject, cache: &mut Cache<'_>) -> ConstantVal { +fn decode_constant_val( + obj: LeanBorrowed<'_>, + cache: &mut Cache<'_>, +) -> ConstantVal { let ctor = obj.as_ctor(); let [name_obj, level_params, typ] = ctor.objs(); let name = decode_name(name_obj, cache.global); let level_params: Vec<_> = collect_list_objs(level_params.as_list()) .into_iter() - .map(|o| decode_name(o, cache.global)) + .map(|o| decode_name(o.borrow(), cache.global)) .collect(); let typ = decode_expr(typ, cache); ConstantVal { name, level_params, typ } } pub fn decode_constant_info( - obj: LeanObject, + obj: LeanBorrowed<'_>, cache: &mut Cache<'_>, ) -> ConstantInfo { let ctor = obj.as_ctor(); @@ -432,7 +421,7 @@ pub fn decode_constant_info( 0 => { let [constant_val, is_unsafe] = inner.objs(); let constant_val = decode_constant_val(constant_val, cache); - let is_unsafe = is_unsafe.as_ptr() as usize == 1; + let is_unsafe = is_unsafe.as_raw() as usize == 1; ConstantInfo::AxiomInfo(AxiomVal { cnst: constant_val, is_unsafe }) }, 1 => { @@ -448,13 +437,13 @@ pub fn decode_constant_info( } else { let hints_ctor = hints.as_ctor(); let [height] = hints_ctor.objs::<1>(); - ReducibilityHints::Regular(height.as_ptr() as u32) + ReducibilityHints::Regular(height.as_raw() as u32) }; let all: Vec<_> = collect_list_objs(all.as_list()) .into_iter() - .map(|o| decode_name(o, cache.global)) + .map(|o| decode_name(o.borrow(), cache.global)) .collect(); - let safety = match safety.as_ptr() as usize { + let safety = match safety.as_raw() as usize { 0 => DefinitionSafety::Unsafe, 1 => DefinitionSafety::Safe, 2 => DefinitionSafety::Partial, @@ -474,7 +463,7 @@ pub fn decode_constant_info( let value = decode_expr(value, cache); let all: Vec<_> = collect_list_objs(all.as_list()) .into_iter() - .map(|o| decode_name(o, cache.global)) + .map(|o| decode_name(o.borrow(), cache.global)) .collect(); ConstantInfo::ThmInfo(TheoremVal { cnst: constant_val, value, all }) }, @@ -484,9 +473,9 @@ pub fn decode_constant_info( let value = decode_expr(value, cache); let all: Vec<_> = collect_list_objs(all.as_list()) .into_iter() - .map(|o| decode_name(o, cache.global)) + .map(|o| decode_name(o.borrow(), cache.global)) .collect(); - let is_unsafe = is_unsafe.as_ptr() as usize == 1; + let is_unsafe = is_unsafe.as_raw() as usize == 1; ConstantInfo::OpaqueInfo(OpaqueVal { cnst: constant_val, value, @@ -497,7 +486,7 @@ pub fn decode_constant_info( 4 => { let [constant_val, kind] = inner.objs(); let constant_val = decode_constant_val(constant_val, cache); - let kind = match kind.as_ptr() as usize { + let kind = match kind.as_raw() as usize { 0 => QuotKind::Type, 1 => QuotKind::Ctor, 2 => QuotKind::Lift, @@ -517,19 +506,19 @@ pub fn decode_constant_info( bools, ] = inner.objs(); let constant_val = decode_constant_val(constant_val, cache); - let num_params = Nat::from_obj(num_params); - let num_indices = Nat::from_obj(num_indices); + let num_params = Nat::from_obj(&num_params); + let num_indices = Nat::from_obj(&num_indices); let all: Vec<_> = collect_list_objs(all.as_list()) .into_iter() - .map(|o| decode_name(o, cache.global)) + .map(|o| decode_name(o.borrow(), cache.global)) .collect(); let ctors: Vec<_> = collect_list_objs(ctors.as_list()) .into_iter() - .map(|o| decode_name(o, cache.global)) + .map(|o| decode_name(o.borrow(), cache.global)) .collect(); - let num_nested = Nat::from_obj(num_nested); + let num_nested = Nat::from_obj(&num_nested); let [is_rec, is_unsafe, is_reflexive, ..] = - (bools.as_ptr() as usize).to_le_bytes().map(|b| b == 1); + (bools.as_raw() as usize).to_le_bytes().map(|b| b == 1); ConstantInfo::InductInfo(InductiveVal { cnst: constant_val, num_params, @@ -547,10 +536,10 @@ pub fn decode_constant_info( inner.objs(); let constant_val = decode_constant_val(constant_val, cache); let induct = decode_name(induct, cache.global); - let cidx = Nat::from_obj(cidx); - let num_params = Nat::from_obj(num_params); - let num_fields = Nat::from_obj(num_fields); - let is_unsafe = is_unsafe.as_ptr() as usize == 1; + let cidx = Nat::from_obj(&cidx); + let num_params = Nat::from_obj(&num_params); + let num_fields = Nat::from_obj(&num_fields); + let is_unsafe = is_unsafe.as_raw() as usize == 1; ConstantInfo::CtorInfo(ConstructorVal { cnst: constant_val, induct, @@ -574,18 +563,18 @@ pub fn decode_constant_info( let constant_val = decode_constant_val(constant_val, cache); let all: Vec<_> = collect_list_objs(all.as_list()) .into_iter() - .map(|o| decode_name(o, cache.global)) + .map(|o| decode_name(o.borrow(), cache.global)) .collect(); - let num_params = Nat::from_obj(num_params); - let num_indices = Nat::from_obj(num_indices); - let num_motives = Nat::from_obj(num_motives); - let num_minors = Nat::from_obj(num_minors); + let num_params = Nat::from_obj(&num_params); + let num_indices = Nat::from_obj(&num_indices); + let num_motives = Nat::from_obj(&num_motives); + let num_minors = Nat::from_obj(&num_minors); let rules: Vec<_> = collect_list_objs(rules.as_list()) .into_iter() - .map(|o| decode_recursor_rule(o, cache)) + .map(|o| decode_recursor_rule(o.borrow(), cache)) .collect(); let [k, is_unsafe, ..] = - (bools.as_ptr() as usize).to_le_bytes().map(|b| b == 1); + (bools.as_raw() as usize).to_le_bytes().map(|b| b == 1); ConstantInfo::RecInfo(RecursorVal { cnst: constant_val, all, @@ -604,7 +593,7 @@ pub fn decode_constant_info( /// Decode a single (Name, ConstantInfo) pair. fn decode_name_constant_info( - obj: LeanObject, + obj: LeanBorrowed<'_>, global: &GlobalCache, ) -> (Name, ConstantInfo) { let mut cache = Cache::new(global); @@ -616,12 +605,27 @@ fn decode_name_constant_info( } // Decode a Lean environment in parallel with hybrid caching. -pub fn decode_env(obj: LeanList) -> Env { - // Phase 1: Collect pointers (sequential) - let objs = collect_list_objs(obj); +pub fn decode_env(list: LeanList>) -> Env { + // Phase 1: Mark entire list graph as MT and collect elements as shared + let shared_list = LeanShared::new(list.inner().to_owned_ref()); + let objs: Vec = shared_list + .borrow() + .as_list() + .iter() + .map(|b| LeanShared::new(b.to_owned_ref())) + .collect(); if objs.len() < PARALLEL_THRESHOLD { - return decode_env_sequential(obj); + // Sequential fallback for small environments + let global = GlobalCache::new(); + let mut env = Env::default(); + env.reserve(objs.len()); + for o in &objs { + let (name, constant_info) = + decode_name_constant_info(o.borrow(), &global); + env.insert(name, constant_info); + } + return env; } // Estimate: ~3 unique names per constant on average @@ -629,11 +633,8 @@ pub fn decode_env(obj: LeanList) -> Env { // Phase 2: Decode in parallel with shared global name cache let pairs: Vec<(Name, ConstantInfo)> = objs - .into_iter() - .map(SendObj) - .collect::>() .into_par_iter() - .map(|o| decode_name_constant_info(o.get(), &global)) + .map(|shared| decode_name_constant_info(shared.borrow(), &global)) .collect(); // Phase 3: Build final map @@ -645,26 +646,15 @@ pub fn decode_env(obj: LeanList) -> Env { env } -/// Sequential fallback for small environments. -pub fn decode_env_sequential(obj: LeanList) -> Env { - let objs = collect_list_objs(obj); - let global = GlobalCache::new(); - let mut env = Env::default(); - env.reserve(objs.len()); - - for o in objs { - let (name, constant_info) = decode_name_constant_info(o, &global); - env.insert(name, constant_info); - } - env -} - // Debug/analysis entry point invoked via the `rust-compile` test flag in // `Tests/FFI/Basic.lean`. Exercises the full compile→decompile→check→serialize // roundtrip and size analysis. Output is intentionally suppressed; re-enable // individual `eprintln!` lines when debugging locally. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] -extern "C" fn rs_tmp_decode_const_map(obj: LeanList) -> usize { +extern "C" fn rs_tmp_decode_const_map( + obj: LeanList>, +) -> usize { // Enable hash-consed size tracking for debugging // TODO: Make this configurable via CLI instead of hardcoded crate::ix::compile::TRACK_HASH_CONSED_SIZE @@ -733,6 +723,7 @@ extern "C" fn rs_tmp_decode_const_map(obj: LeanList) -> usize { env.as_ref().len() } +#[cfg(feature = "test-ffi")] /// Size breakdown for a constant: alpha-invariant vs metadata #[derive(Default, Clone)] struct ConstSizeBreakdown { @@ -740,12 +731,14 @@ struct ConstSizeBreakdown { meta_size: usize, // Metadata (names, binder info, etc.) } +#[cfg(feature = "test-ffi")] impl ConstSizeBreakdown { fn total(&self) -> usize { self.alpha_size + self.meta_size } } +#[cfg(feature = "test-ffi")] /// Analyze the serialized size of a constant and its transitive dependencies. fn analyze_const_size(stt: &crate::ix::compile::CompileState, name_str: &str) { use crate::ix::address::Address; @@ -895,6 +888,7 @@ fn analyze_const_size(stt: &crate::ix::compile::CompileState, name_str: &str) { } /// Build a name index for metadata serialization. +#[cfg(feature = "test-ffi")] fn build_name_index( stt: &crate::ix::compile::CompileState, ) -> crate::ix::ixon::metadata::NameIndex { @@ -918,6 +912,7 @@ fn build_name_index( } /// Compute size breakdown for a constant (alpha-invariant vs metadata). +#[cfg(feature = "test-ffi")] fn compute_const_size_breakdown( constant: &crate::ix::ixon::constant::Constant, name: &Name, @@ -938,6 +933,7 @@ fn compute_const_size_breakdown( } /// Compute the serialized size of constant metadata. +#[cfg(feature = "test-ffi")] fn serialized_meta_size( meta: &crate::ix::ixon::metadata::ConstantMeta, name_index: &crate::ix::ixon::metadata::NameIndex, @@ -950,6 +946,7 @@ fn serialized_meta_size( } /// Parse a dotted name string into a Name. +#[cfg(feature = "test-ffi")] fn parse_name(s: &str) -> Name { let parts: Vec<&str> = s.split('.').collect(); let mut name = Name::anon(); @@ -960,6 +957,7 @@ fn parse_name(s: &str) -> Name { } /// Compute the serialized size of a constant. +#[cfg(feature = "test-ffi")] fn serialized_const_size( constant: &crate::ix::ixon::constant::Constant, ) -> usize { @@ -969,6 +967,7 @@ fn serialized_const_size( } /// Analyze block size statistics: hash-consing vs serialization. +#[cfg(feature = "test-ffi")] fn analyze_block_size_stats(stt: &crate::ix::compile::CompileState) { use crate::ix::compile::BlockSizeStats; @@ -1153,6 +1152,7 @@ fn analyze_block_size_stats(stt: &crate::ix::compile::CompileState) { } /// Truncate a name for display. +#[cfg(feature = "test-ffi")] fn truncate_name(name: &str, max_len: usize) -> String { if name.len() <= max_len { name.to_string() diff --git a/src/ffi/primitives.rs b/src/ffi/primitives.rs index 07d4e9e8..5f6088b5 100644 --- a/src/ffi/primitives.rs +++ b/src/ffi/primitives.rs @@ -4,7 +4,9 @@ //! (ix-specific types not covered by the lean-ffi test suite). use lean_ffi::nat::Nat; -use lean_ffi::object::{LeanArray, LeanCtor, LeanObject}; +use lean_ffi::object::{LeanBorrowed, LeanRef}; +#[cfg(feature = "test-ffi")] +use lean_ffi::object::{LeanArray, LeanCtor, LeanOwned}; // ============================================================================= // AssocList / HashMap roundtrip FFI functions @@ -13,42 +15,45 @@ use lean_ffi::object::{LeanArray, LeanCtor, LeanObject}; /// Round-trip an AssocList Nat Nat. /// AssocList: nil (tag 0, 0 fields) | cons key value tail (tag 1, 3 fields) /// Note: nil with 0 fields may be represented as lean_box(0) +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_assoclist_nat_nat( - list_ptr: LeanObject, -) -> LeanObject { + list_ptr: LeanBorrowed<'_>, +) -> LeanOwned { if list_ptr.is_scalar() { - return LeanObject::box_usize(0); + return LeanOwned::box_usize(0); } - let pairs = decode_assoc_list_nat_nat(list_ptr); + let pairs = decode_assoc_list_nat_nat(&list_ptr); build_assoc_list_nat_nat(&pairs) } +#[cfg(feature = "test-ffi")] /// Build an AssocList Nat Nat from pairs -fn build_assoc_list_nat_nat(pairs: &[(Nat, Nat)]) -> LeanObject { +fn build_assoc_list_nat_nat(pairs: &[(Nat, Nat)]) -> LeanOwned { // Build in reverse to preserve order - let mut list = LeanObject::box_usize(0); // nil + let mut list = LeanOwned::box_usize(0); // nil for (k, v) in pairs.iter().rev() { let cons = LeanCtor::alloc(1, 3, 0); // AssocList.cons cons.set(0, Nat::to_lean(k)); cons.set(1, Nat::to_lean(v)); cons.set(2, list); - list = *cons; + list = cons.into(); } list } /// Round-trip a DHashMap.Raw Nat Nat. +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_dhashmap_raw_nat_nat( - raw_ptr: LeanObject, -) -> LeanObject { + raw_ptr: LeanBorrowed<'_>, +) -> LeanOwned { if raw_ptr.is_scalar() { - return raw_ptr; + return raw_ptr.to_owned_ref(); } let raw_ctor = raw_ptr.as_ctor(); - let size = Nat::from_obj(raw_ctor.get(0)); + let size = Nat::from_obj(&raw_ctor.get(0)); let buckets = raw_ctor.get(1).as_array(); // Decode and rebuild buckets @@ -56,15 +61,15 @@ pub extern "C" fn rs_roundtrip_dhashmap_raw_nat_nat( let mut all_pairs: Vec<(Nat, Nat)> = Vec::new(); for bucket in buckets.iter() { - let pairs = decode_assoc_list_nat_nat(bucket); + let pairs = decode_assoc_list_nat_nat(&bucket); all_pairs.extend(pairs); } // Rebuild buckets let new_buckets = LeanArray::alloc(num_buckets); - let nil = LeanObject::box_usize(0); + let nil = LeanOwned::box_usize(0); for i in 0..num_buckets { - new_buckets.set(i, nil); + new_buckets.set(i, nil.clone()); } for (k, v) in &all_pairs { @@ -78,19 +83,19 @@ pub extern "C" fn rs_roundtrip_dhashmap_raw_nat_nat( #[allow(clippy::cast_possible_truncation)] let bucket_idx = (k_u64 as usize) & (num_buckets - 1); - let old_bucket = new_buckets.get(bucket_idx); + let old_bucket = new_buckets.get(bucket_idx).to_owned_ref(); let new_bucket = LeanCtor::alloc(1, 3, 0); new_bucket.set(0, Nat::to_lean(k)); new_bucket.set(1, Nat::to_lean(v)); new_bucket.set(2, old_bucket); - new_buckets.set(bucket_idx, *new_bucket); + new_buckets.set(bucket_idx, new_bucket); } // Build Raw let raw = LeanCtor::alloc(0, 2, 0); raw.set(0, Nat::to_lean(&size)); - raw.set(1, *new_buckets); - *raw + raw.set(1, new_buckets); + raw.into() } /// Round-trip a Std.HashMap Nat Nat. @@ -108,19 +113,20 @@ pub extern "C" fn rs_roundtrip_dhashmap_raw_nat_nat( /// - AssocList: /// - nil: lean_box(0) /// - cons key value tail: ctor 1, 3 fields +#[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] pub extern "C" fn rs_roundtrip_hashmap_nat_nat( - map_ptr: LeanCtor, -) -> LeanObject { + map_ptr: LeanCtor>, +) -> LeanOwned { // Due to unboxing, map_ptr points directly to Raw - let size = Nat::from_obj(map_ptr.get(0)); + let size = Nat::from_obj(&map_ptr.get(0)); let buckets = map_ptr.get(1).as_array(); // Decode buckets (Array of AssocLists) let mut pairs: Vec<(Nat, Nat)> = Vec::new(); for bucket in buckets.iter() { - let bucket_pairs = decode_assoc_list_nat_nat(bucket); + let bucket_pairs = decode_assoc_list_nat_nat(&bucket); pairs.extend(bucket_pairs); } @@ -129,9 +135,9 @@ pub extern "C" fn rs_roundtrip_hashmap_nat_nat( let new_buckets = LeanArray::alloc(num_buckets); // Initialize all buckets to AssocList.nil (lean_box(0)) - let nil = LeanObject::box_usize(0); + let nil = LeanOwned::box_usize(0); for i in 0..num_buckets { - new_buckets.set(i, nil); + new_buckets.set(i, nil.clone()); } // Insert each pair into the appropriate bucket using Lean's hash function @@ -150,45 +156,46 @@ pub extern "C" fn rs_roundtrip_hashmap_nat_nat( let bucket_idx = (k_u64 as usize) & (num_buckets - 1); // Get current bucket AssocList - let old_bucket = new_buckets.get(bucket_idx); + let old_bucket = new_buckets.get(bucket_idx).to_owned_ref(); // Build AssocList.cons key value tail (tag 1, 3 fields) let new_bucket = LeanCtor::alloc(1, 3, 0); new_bucket.set(0, Nat::to_lean(k)); new_bucket.set(1, Nat::to_lean(v)); new_bucket.set(2, old_bucket); - new_buckets.set(bucket_idx, *new_bucket); + new_buckets.set(bucket_idx, new_bucket); } // Build Raw (ctor 0, 2 fields: size, buckets) // Due to unboxing, this IS the HashMap let raw = LeanCtor::alloc(0, 2, 0); raw.set(0, Nat::to_lean(&size)); - raw.set(1, *new_buckets); - *raw + raw.set(1, new_buckets); + raw.into() } /// Decode a Lean AssocList Nat Nat to Vec of pairs /// AssocList: nil (tag 0) | cons key value tail (tag 1, 3 fields) -pub fn decode_assoc_list_nat_nat(obj: LeanObject) -> Vec<(Nat, Nat)> { +pub fn decode_assoc_list_nat_nat(obj: &impl LeanRef) -> Vec<(Nat, Nat)> { let mut result = Vec::new(); - let mut current = obj; + let mut current_ptr = obj.as_raw(); loop { - if current.is_scalar() { + if current_ptr as usize & 1 == 1 { break; } + let current = unsafe { LeanBorrowed::from_raw(current_ptr) }; let ctor = current.as_ctor(); if ctor.tag() == 0 { break; } - let k = Nat::from_obj(ctor.get(0)); - let v = Nat::from_obj(ctor.get(1)); + let k = Nat::from_obj(&ctor.get(0)); + let v = Nat::from_obj(&ctor.get(1)); result.push((k, v)); - current = ctor.get(2); + current_ptr = ctor.get(2).as_raw(); } result diff --git a/src/ffi/refcount.rs b/src/ffi/refcount.rs new file mode 100644 index 00000000..476b1782 --- /dev/null +++ b/src/ffi/refcount.rs @@ -0,0 +1,431 @@ +//! Reference counting and ownership tests for the typed lean-ffi API. +//! +//! Gated behind the `test-ffi` Cargo feature. These FFI functions exercise +//! ownership semantics that would catch double-free, use-after-free, and +//! refcount leaks: +//! +//! - Borrowed params (`@&`): must NOT lean_dec on drop +//! - to_owned_ref: must lean_inc correctly +//! - Clone on LeanOwned: must lean_inc correctly +//! - Owned drop: must lean_dec correctly +//! - Nested field access from borrowed refs +//! - Cache deduplication via Clone +//! - Repeated alloc/dealloc cycles + +use std::thread; + +use lean_ffi::LeanShared; +use lean_ffi::nat::Nat; +use lean_ffi::object::{ + LeanArray, LeanBorrowed, LeanList, LeanOwned, LeanRef, LeanString, +}; + +use crate::ffi::builder::LeanBuildCache; +use crate::lean::{LeanIxExpr, LeanIxLevel, LeanIxName}; + +// ============================================================================= +// Borrow-to-owned: take @&, return owned copy via to_owned_ref +// ============================================================================= + +/// Take a borrowed Ix.Name, convert to owned via to_owned_ref, return it. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_borrow_to_owned_name( + name: LeanIxName>, +) -> LeanIxName { + LeanIxName::new(name.inner().to_owned_ref()) +} + +/// Same for Ix.Level. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_borrow_to_owned_level( + level: LeanIxLevel>, +) -> LeanIxLevel { + LeanIxLevel::new(level.inner().to_owned_ref()) +} + +/// Same for Ix.Expr. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_borrow_to_owned_expr( + expr: LeanIxExpr>, +) -> LeanIxExpr { + LeanIxExpr::new(expr.inner().to_owned_ref()) +} + +// ============================================================================= +// Multiple borrows from same parent +// ============================================================================= + +/// Read all fields of an Ix.Name.str twice, return concatenated strings. +/// Tests that multiple .get() calls from the same ctor don't corrupt. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_multi_borrow_name( + name: LeanIxName>, +) -> LeanString { + let ctor = name.as_ctor(); + let _parent = ctor.get(0); + let s = ctor.get(1); + let _hash = ctor.get(2); + let s2 = ctor.get(1); + let result = format!("{}|{}", s.as_string(), s2.as_string()); + LeanString::new(&result) +} + +// ============================================================================= +// Deep borrow traversal +// ============================================================================= + +/// Count nodes in an Ix.Expr tree via borrowed traversal. Returns count as Nat. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_deep_borrow_expr( + expr: LeanIxExpr>, +) -> LeanOwned { + fn count_nodes(expr: &LeanIxExpr) -> u64 { + let ctor = expr.as_ctor(); + match ctor.tag() { + 5 => { + // app: fn, arg, hash + let fn_expr = LeanIxExpr(ctor.get(0)); + let arg_expr = LeanIxExpr(ctor.get(1)); + 1 + count_nodes(&fn_expr) + count_nodes(&arg_expr) + }, + _ => 1, + } + } + Nat::from(count_nodes(&expr)).to_lean().into() +} + +// ============================================================================= +// Owned drop: take owned, extract data, verify drop doesn't crash +// ============================================================================= + +/// Take an owned Array of Names, decode them all, return count. +/// The owned array is dropped at the end → lean_dec_ref on each element. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_owned_array_drop( + arr: LeanArray, +) -> LeanOwned { + let count = arr.len(); + for i in 0..count { + let _ = LeanIxName(arr.get(i)).decode(); + } + Nat::from(count as u64).to_lean().into() +} + +/// Take an owned List of Exprs, consume and count. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_owned_list_drop( + list: LeanList, +) -> LeanOwned { + let mut count: u64 = 0; + for elem in list.iter() { + let _ = LeanIxExpr(elem).decode(); + count += 1; + } + Nat::from(count).to_lean().into() +} + +// ============================================================================= +// Clone then drop: clone an owned value, decode both independently +// ============================================================================= + +/// Take a borrowed Name, make two owned clones, decode both, +/// verify they produce the same result. Returns 1 if equal, 0 otherwise. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_clone_and_compare( + name: LeanIxName>, +) -> LeanOwned { + let owned1 = LeanIxName::new(name.inner().to_owned_ref()); + let owned2 = owned1.clone(); + let decoded1 = owned1.decode(); + let decoded2 = owned2.decode(); + // owned1 and owned2 both dropped → two lean_dec calls + Nat::from(if decoded1 == decoded2 { 1u64 } else { 0u64 }).to_lean().into() +} + +// ============================================================================= +// Roundtrip loop: stresses alloc/dealloc cycles +// ============================================================================= + +/// Roundtrip an Ix.Name N times. Each iteration: decode → build → drop old. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_roundtrip_loop( + name: LeanIxName>, + n: usize, +) -> LeanIxName { + let mut rust_name = name.decode(); + for _ in 0..n { + let mut cache = LeanBuildCache::new(); + let lean_name = LeanIxName::build(&mut cache, &rust_name); + rust_name = lean_name.decode(); + // lean_name dropped here → lean_dec_ref + } + let mut cache = LeanBuildCache::new(); + LeanIxName::build(&mut cache, &rust_name) +} + +// ============================================================================= +// Nested collection traversal with borrows +// ============================================================================= + +/// Take a borrowed Array of (Name × Level) pairs, decode all, return count. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_nested_borrow( + arr: LeanArray>, +) -> LeanOwned { + let mut count: u64 = 0; + for elem in arr.iter() { + let pair = elem.as_ctor(); + let _name = LeanIxName(pair.get(0)).decode(); + let _level = LeanIxLevel(pair.get(1)).decode(); + count += 1; + } + Nat::from(count).to_lean().into() +} + +// ============================================================================= +// Cache deduplication: build same subterm multiple times +// ============================================================================= + +/// Build an array of N copies of the same Name from cache. +/// The LeanBuildCache deduplicates via Clone (lean_inc). +/// Each element's lean_dec on drop must work correctly. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_cache_dedup( + name: LeanIxName>, + n: usize, +) -> LeanArray { + let decoded = name.decode(); + let mut cache = LeanBuildCache::new(); + let arr = LeanArray::alloc(n); + for i in 0..n { + let lean_name = LeanIxName::build(&mut cache, &decoded); + arr.set(i, lean_name); + } + arr +} + +// ============================================================================= +// Persistent object handling +// ============================================================================= + +/// Check if a borrowed Name is persistent (m_rc == 0). +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_is_persistent( + name: LeanIxName>, +) -> u8 { + if name.inner().is_persistent() { 1 } else { 0 } +} + +/// Decode a persistent Name and rebuild it. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_persistent_roundtrip( + name: LeanIxName>, +) -> LeanIxName { + let decoded = name.decode(); + let mut cache = LeanBuildCache::new(); + LeanIxName::build(&mut cache, &decoded) +} + +// ============================================================================= +// String lifecycle +// ============================================================================= + +/// Build a new string from borrowed input. The output is independent. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_string_lifecycle( + s: LeanString>, +) -> LeanString { + let rust_string = s.to_string(); + let reversed: String = rust_string.chars().rev().collect(); + LeanString::new(&reversed) +} + +// ============================================================================= +// Build and immediately discard: stress alloc then drop +// ============================================================================= + +/// Build N Lean Names from a borrowed input, immediately drop each. +/// Returns the input unchanged. Stresses alloc/dealloc without leaking. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_alloc_drop_loop( + name: LeanIxName>, + n: usize, +) -> LeanIxName { + let decoded = name.decode(); + for _ in 0..n { + let mut cache = LeanBuildCache::new(); + let _lean_name = LeanIxName::build(&mut cache, &decoded); + // _lean_name dropped immediately + } + let mut cache = LeanBuildCache::new(); + LeanIxName::build(&mut cache, &decoded) +} + +// ============================================================================= +// Array build and element extraction +// ============================================================================= + +/// Build an array of Levels, extract each via borrowed access, rebuild. +/// Tests that borrowed array elements survive while array is alive. +#[unsafe(no_mangle)] +pub extern "C" fn rs_refcount_array_element_borrow( + arr: LeanArray>, +) -> LeanArray { + let len = arr.len(); + let out = LeanArray::alloc(len); + for i in 0..len { + let borrowed_elem = arr.get(i); + let level = LeanIxLevel(borrowed_elem).decode(); + let mut cache = LeanBuildCache::new(); + out.set(i, LeanIxLevel::build(&mut cache, &level)); + } + out +} + +// ============================================================================= +// Multi-threaded tests using LeanShared on ix domain types +// ============================================================================= + +/// Mark an array of Ix.Names as MT, decode in parallel from N threads. +/// Each thread decodes all names and returns the count. +/// Tests LeanShared + lean_mark_mt on complex Ix object graphs. +#[unsafe(no_mangle)] +pub extern "C" fn rs_mt_parallel_decode_names( + arr: LeanArray>, + n_threads: usize, +) -> LeanOwned { + let shared = LeanShared::new(arr.inner().to_owned_ref()); + + let handles: Vec<_> = (0..n_threads) + .map(|_| { + let shared_clone = shared.clone(); + thread::spawn(move || { + let borrowed_arr = shared_clone.borrow().as_array(); + let mut count: u64 = 0; + for elem in borrowed_arr.iter() { + let _name = LeanIxName(elem).decode(); + count += 1; + } + count + }) + }) + .collect(); + + let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum(); + Nat::from(total).to_lean().into() +} + +/// Mark an array of Ix.Exprs as MT, decode in parallel from N threads. +/// Each thread decodes all exprs and counts nodes. +#[unsafe(no_mangle)] +pub extern "C" fn rs_mt_parallel_decode_exprs( + arr: LeanArray>, + n_threads: usize, +) -> LeanOwned { + fn count_expr_nodes(expr: &LeanIxExpr) -> u64 { + let ctor = expr.as_ctor(); + match ctor.tag() { + 5 => { + // app + 1 + count_expr_nodes(&LeanIxExpr(ctor.get(0))) + + count_expr_nodes(&LeanIxExpr(ctor.get(1))) + }, + _ => 1, + } + } + + let shared = LeanShared::new(arr.inner().to_owned_ref()); + + let handles: Vec<_> = (0..n_threads) + .map(|_| { + let shared_clone = shared.clone(); + thread::spawn(move || { + let borrowed_arr = shared_clone.borrow().as_array(); + let mut total: u64 = 0; + for elem in borrowed_arr.iter() { + total += count_expr_nodes(&LeanIxExpr(elem)); + } + total + }) + }) + .collect(); + + let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum(); + Nat::from(total).to_lean().into() +} + +/// Parallel roundtrip: mark array of Names as MT, each thread decodes +/// all names and rebuilds them. Returns an array of rebuilt names from +/// one thread. Tests that building new Lean objects while reading +/// MT-marked objects works correctly. +#[unsafe(no_mangle)] +pub extern "C" fn rs_mt_parallel_roundtrip_names( + arr: LeanArray>, + n_threads: usize, +) -> LeanArray { + use crate::ix::env::Name; + + let shared = LeanShared::new(arr.inner().to_owned_ref()); + let len = arr.len(); + + let handles: Vec<_> = (0..n_threads) + .map(|_| { + let shared_clone = shared.clone(); + thread::spawn(move || { + let borrowed_arr = shared_clone.borrow().as_array(); + let mut decoded: Vec = Vec::with_capacity(len); + for elem in borrowed_arr.iter() { + decoded.push(LeanIxName(elem).decode()); + } + decoded + }) + }) + .collect(); + + // Collect results from all threads, use the first one to rebuild + let all_results: Vec> = + handles.into_iter().map(|h| h.join().unwrap()).collect(); + + // Verify all threads decoded the same values + let first = &all_results[0]; + for result in &all_results[1..] { + assert_eq!(first, result, "MT decode inconsistency"); + } + + // Rebuild from the first thread's results + let mut cache = LeanBuildCache::new(); + let out = LeanArray::alloc(first.len()); + for (i, name) in first.iter().enumerate() { + out.set(i, LeanIxName::build(&mut cache, name)); + } + out +} + +/// Stress test: clone/drop LeanShared rapidly from many threads +/// on a complex Ix.Expr graph. +#[unsafe(no_mangle)] +pub extern "C" fn rs_mt_shared_expr_stress( + expr: LeanIxExpr>, + n_threads: usize, + clones_per_thread: usize, +) -> LeanOwned { + let shared = LeanShared::new(expr.inner().to_owned_ref()); + + let handles: Vec<_> = (0..n_threads) + .map(|_| { + let shared_clone = shared.clone(); + thread::spawn(move || { + for _ in 0..clones_per_thread { + let tmp = shared_clone.clone(); + // Borrow and read tag to ensure the object is valid + let _ = tmp.borrow().as_ctor().tag(); + // tmp dropped → atomic lean_dec + } + 1u64 + }) + }) + .collect(); + + let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum(); + Nat::from(total).to_lean().into() +} diff --git a/src/ffi/unsigned.rs b/src/ffi/unsigned.rs index ffc44d25..57b5919e 100644 --- a/src/ffi/unsigned.rs +++ b/src/ffi/unsigned.rs @@ -1,21 +1,21 @@ -use lean_ffi::object::LeanByteArray; +use lean_ffi::object::{LeanByteArray, LeanOwned}; #[unsafe(no_mangle)] -extern "C" fn c_u16_to_le_bytes(v: u16) -> LeanByteArray { +extern "C" fn c_u16_to_le_bytes(v: u16) -> LeanByteArray { LeanByteArray::from_bytes(&v.to_le_bytes()) } #[unsafe(no_mangle)] -extern "C" fn c_u32_to_le_bytes(v: u32) -> LeanByteArray { +extern "C" fn c_u32_to_le_bytes(v: u32) -> LeanByteArray { LeanByteArray::from_bytes(&v.to_le_bytes()) } #[unsafe(no_mangle)] -extern "C" fn c_u64_to_le_bytes(v: u64) -> LeanByteArray { +extern "C" fn c_u64_to_le_bytes(v: u64) -> LeanByteArray { LeanByteArray::from_bytes(&v.to_le_bytes()) } #[unsafe(no_mangle)] -extern "C" fn c_usize_to_le_bytes(v: usize) -> LeanByteArray { +extern "C" fn c_usize_to_le_bytes(v: usize) -> LeanByteArray { LeanByteArray::from_bytes(&v.to_le_bytes()) } diff --git a/src/lean.rs b/src/lean.rs index c75e5a8b..d2d4619b 100644 --- a/src/lean.rs +++ b/src/lean.rs @@ -3,6 +3,8 @@ //! Generic Lean FFI wrappers live in the `lean_ffi` crate. This module defines //! typed newtypes for ix-specific Lean types using `lean_ffi::lean_domain_type!`. +use lean_ffi::object::{LeanBorrowed, LeanByteArray, LeanOwned, LeanRef}; + lean_ffi::lean_domain_type! { // Ix core types /// Lean `Ix.Name` object. @@ -132,35 +134,62 @@ lean_ffi::lean_domain_type! { } /// Lean `Address` object — newtype over `LeanByteArray`. -#[derive(Clone, Copy)] +/// +/// Address is a single-field struct in Lean, so it's unboxed to ByteArray +/// at the FFI boundary. #[repr(transparent)] -pub struct LeanIxAddress(lean_ffi::object::LeanByteArray); +pub struct LeanIxAddress(LeanByteArray); + +impl Clone for LeanIxAddress { + #[inline] + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Copy for LeanIxAddress {} -impl std::ops::Deref for LeanIxAddress { - type Target = lean_ffi::object::LeanByteArray; +impl std::ops::Deref for LeanIxAddress { + type Target = LeanByteArray; #[inline] - fn deref(&self) -> &lean_ffi::object::LeanByteArray { + fn deref(&self) -> &LeanByteArray { &self.0 } } -impl From for lean_ffi::object::LeanObject { +impl From> for LeanOwned { #[inline] - fn from(x: LeanIxAddress) -> Self { + fn from(x: LeanIxAddress) -> Self { x.0.into() } } -impl From for LeanIxAddress { +impl<'a> From>> + for LeanIxAddress> +{ #[inline] - fn from(x: lean_ffi::object::LeanByteArray) -> Self { + fn from(x: LeanByteArray>) -> Self { Self(x) } } -impl LeanIxAddress { +impl From> for LeanIxAddress { + #[inline] + fn from(x: LeanByteArray) -> Self { + Self(x) + } +} + +impl LeanIxAddress { + #[inline] + pub fn new(ba: LeanByteArray) -> Self { + Self(ba) + } +} + +impl<'a> LeanIxAddress> { #[inline] - pub fn new(obj: lean_ffi::object::LeanObject) -> Self { - Self(obj.as_byte_array()) + pub fn from_borrowed(ba: LeanByteArray>) -> Self { + Self(ba) } } diff --git a/src/sha256.rs b/src/sha256.rs index fef0f35d..c660ae9c 100644 --- a/src/sha256.rs +++ b/src/sha256.rs @@ -1,9 +1,11 @@ use sha2::{Digest, Sha256}; -use lean_ffi::object::LeanByteArray; +use lean_ffi::object::{LeanBorrowed, LeanByteArray, LeanOwned}; #[unsafe(no_mangle)] -extern "C" fn rs_sha256(bytes: LeanByteArray) -> LeanByteArray { +extern "C" fn rs_sha256( + bytes: LeanByteArray>, +) -> LeanByteArray { let mut hasher = Sha256::new(); hasher.update(bytes.as_bytes()); let digest = hasher.finalize(); From b36620c0f855458aba911cd019fb9e1bfca44972 Mon Sep 17 00:00:00 2001 From: samuelburnham <45365069+samuelburnham@users.noreply.github.com> Date: Fri, 20 Mar 2026 12:02:56 -0400 Subject: [PATCH 2/7] Cleanup --- Cargo.lock | 2 +- Cargo.toml | 2 +- Tests/FFI/Refcount.lean | 18 +++++----- src/ffi.rs | 25 +------------- src/ffi/compile.rs | 35 ++++++++++--------- src/ffi/graph.rs | 31 +++++++---------- src/ffi/ix/env.rs | 39 +++++++++------------ src/ffi/ixon/compare.rs | 12 ++----- src/ffi/lean_env.rs | 75 +++++++++++++++++++++++------------------ src/ffi/primitives.rs | 28 ++++++--------- src/ffi/refcount.rs | 1 + 11 files changed, 116 insertions(+), 152 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3ade3fc..0b31e821 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1846,7 +1846,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lean-ffi" version = "0.1.0" -source = "git+https://github.com/argumentcomputer/lean-ffi?rev=53c90ecf051023158b727e926a6141e72b106395#53c90ecf051023158b727e926a6141e72b106395" +source = "git+https://github.com/argumentcomputer/lean-ffi?rev=ffbbc0882afa1162c0806c8a5c63ea5da631dd6c#ffbbc0882afa1162c0806c8a5c63ea5da631dd6c" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index 55a915a5..df2b2dda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ anyhow = "1" blake3 = "1.8.2" itertools = "0.14.0" indexmap = { version = "2", features = ["rayon"] } -lean-ffi = { git = "https://github.com/argumentcomputer/lean-ffi", rev = "53c90ecf051023158b727e926a6141e72b106395" } +lean-ffi = { git = "https://github.com/argumentcomputer/lean-ffi", rev = "ffbbc0882afa1162c0806c8a5c63ea5da631dd6c" } multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "bdb0d7d66c02b554e66c449da2dbf12dc0dc27af" } num-bigint = "0.4.6" rayon = "1" diff --git a/Tests/FFI/Refcount.lean b/Tests/FFI/Refcount.lean index 7c012042..0ad4fe3f 100644 --- a/Tests/FFI/Refcount.lean +++ b/Tests/FFI/Refcount.lean @@ -1,6 +1,7 @@ /- Reference counting and ownership tests for the typed lean-ffi API. - Requires `IX_TEST_FFI=1` to enable the `test-ffi` Cargo feature. + Gated behind the `test-ffi` Cargo feature (included by default, + stripped with `IX_RELEASE=1`). These tests exercise ownership semantics that catch double-free, use-after-free, and refcount leaks by calling Rust FFI functions that: @@ -11,7 +12,8 @@ - Traverse deeply nested borrows without inc_ref - Build from cache (clone-based dedup) then drop the array - Repeatedly alloc and immediately drop (stress alloc/dealloc) - - Handle persistent objects (m_rc == 0, inc/dec are no-ops) + - Handle persistent objects (m_rc == 0, lean_inc/lean_dec are no-ops) + - Mark objects as multi-threaded and decode from parallel threads -/ module @@ -45,7 +47,7 @@ opaque multiBorrowName : @& Ix.Name → String @[extern "rs_refcount_deep_borrow_expr"] opaque deepBorrowExpr : @& Ix.Expr → Nat --- Owned drop (NOT @&, Rust must lean_dec) +-- Owned drop (NOT @&, Rust takes ownership and drops) @[extern "rs_refcount_owned_array_drop"] opaque ownedArrayDrop : Array Ix.Name → Nat @@ -148,8 +150,8 @@ def deepBorrowTests : TestSeq := test "deep borrow expr 7 nodes" (deepBorrowExpr app3 == 7) /-! ## Owned drop tests - Passes owned (NOT @&) arrays/lists. Rust must lean_dec each element - on drop. A missing lean_dec leaks; an extra lean_dec crashes. -/ + Passes owned (NOT @&) arrays/lists. Rust drops each element + on scope exit. A missing drop leaks; an extra drop crashes. -/ def ownedDropTests : TestSeq := let names := #[testAnon, testStr, testNum] @@ -194,7 +196,7 @@ def nestedBorrowTests : TestSeq := test "nested borrow empty" (nestedBorrow #[] == 0) /-! ## Persistent object tests - Module-level defs have m_rc == 0. lean_inc/lean_dec are no-ops. + Module-level defs have m_rc == 0. Refcount ops are no-ops. Verifies that the typed API handles these correctly. -/ def persistentTests : TestSeq := @@ -203,8 +205,8 @@ def persistentTests : TestSeq := /-! ## Cache dedup tests Builds N copies of the same Name via LeanBuildCache, which clones - (lean_inc) cached entries. Dropping the array lean_decs each copy. - Would crash if clone refcount was wrong. -/ + cached entries (incrementing refcount). Dropping the array decrements + each copy. Would crash if clone refcount was wrong. -/ def cacheDedupTests : TestSeq := let duped := cacheDedup testStr 5 diff --git a/src/ffi.rs b/src/ffi.rs index 64558b13..bed68a8e 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -26,31 +26,8 @@ pub mod primitives; // Primitives: rs_roundtrip_nat, rs_roundtrip_string, etc. #[cfg(feature = "test-ffi")] pub mod refcount; // Reference counting / ownership tests (test-only) -use lean_ffi::object::{LeanIOResult, LeanOwned}; #[cfg(feature = "test-ffi")] -use lean_ffi::object::{LeanArray, LeanBorrowed, LeanByteArray, LeanRef}; - -/// Guard an FFI function that returns a Lean IO result against panics. -/// On panic, returns a Lean IO error with the panic message instead of -/// unwinding across the `extern "C"` boundary (which is undefined behavior). -pub(crate) fn ffi_io_guard(f: F) -> LeanIOResult -where - F: FnOnce() -> LeanIOResult + std::panic::UnwindSafe, -{ - match std::panic::catch_unwind(f) { - Ok(result) => result, - Err(panic_info) => { - let msg = if let Some(s) = panic_info.downcast_ref::<&str>() { - format!("FFI panic: {s}") - } else if let Some(s) = panic_info.downcast_ref::() { - format!("FFI panic: {s}") - } else { - "FFI panic: unknown".to_string() - }; - LeanIOResult::error_string(&msg) - }, - } -} +use lean_ffi::object::{LeanArray, LeanBorrowed, LeanByteArray, LeanOwned, LeanRef}; #[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] diff --git a/src/ffi/compile.rs b/src/ffi/compile.rs index bd8b09f0..425a9bf8 100644 --- a/src/ffi/compile.rs +++ b/src/ffi/compile.rs @@ -9,7 +9,6 @@ use std::sync::Arc; -use crate::ffi::ffi_io_guard; use crate::ix::address::Address; use crate::ix::compile::{CompileState, compile_env}; use crate::ix::condense::compute_sccs; @@ -201,7 +200,7 @@ pub extern "C" fn rs_roundtrip_block_compare_detail( pub extern "C" fn rs_compile_env_full( env_consts_ptr: LeanList>, ) -> LeanIOResult { - ffi_io_guard(std::panic::AssertUnwindSafe(|| { + { // Phase 1: Decode Lean environment let rust_env = decode_env(env_consts_ptr); let env_len = rust_env.len(); @@ -293,7 +292,7 @@ pub extern "C" fn rs_compile_env_full( result.set(2, compiled_obj); LeanIOResult::ok(result) - })) + } } /// FFI function to compile a Lean environment to serialized Ixon.Env bytes. @@ -301,7 +300,7 @@ pub extern "C" fn rs_compile_env_full( pub extern "C" fn rs_compile_env( env_consts_ptr: LeanList>, ) -> LeanIOResult { - ffi_io_guard(std::panic::AssertUnwindSafe(|| { + { let rust_env = decode_env(env_consts_ptr); let rust_env = Arc::new(rust_env); @@ -323,7 +322,7 @@ pub extern "C" fn rs_compile_env( // Build Lean ByteArray let ba = LeanByteArray::from_bytes(&buf); LeanIOResult::ok(ba) - })) + } } /// Round-trip a RawEnv: decode from Lean, re-encode via builder. @@ -342,7 +341,7 @@ pub extern "C" fn rs_roundtrip_raw_env( pub extern "C" fn rs_compile_phases( env_consts_ptr: LeanList>, ) -> LeanIOResult { - ffi_io_guard(std::panic::AssertUnwindSafe(|| { + { let rust_env = decode_env(env_consts_ptr); let env_len = rust_env.len(); let rust_env = Arc::new(rust_env); @@ -434,7 +433,7 @@ pub extern "C" fn rs_compile_phases( result.set(2, raw_ixon_env); LeanIOResult::ok(result) - })) + } } /// FFI function to compile a Lean environment to a RawEnv. @@ -442,7 +441,7 @@ pub extern "C" fn rs_compile_phases( pub extern "C" fn rs_compile_env_to_ixon( env_consts_ptr: LeanList>, ) -> LeanIOResult { - ffi_io_guard(std::panic::AssertUnwindSafe(|| { + { let rust_env = decode_env(env_consts_ptr); let rust_env = Arc::new(rust_env); @@ -520,7 +519,7 @@ pub extern "C" fn rs_compile_env_to_ixon( result.set(3, comms_arr); result.set(4, names_arr); LeanIOResult::ok(result) - })) + } } /// FFI function to canonicalize environment to Ix.RawEnvironment. @@ -528,12 +527,12 @@ pub extern "C" fn rs_compile_env_to_ixon( pub extern "C" fn rs_canonicalize_env_to_ix( env_consts_ptr: LeanList>, ) -> LeanIOResult { - ffi_io_guard(std::panic::AssertUnwindSafe(|| { + { let rust_env = decode_env(env_consts_ptr); let mut cache = LeanBuildCache::with_capacity(rust_env.len()); let raw_env = LeanIxRawEnvironment::build(&mut cache, &rust_env); LeanIOResult::ok(raw_env) - })) + } } // ============================================================================= @@ -631,7 +630,7 @@ extern "C" fn rs_compare_block( } let global_cache = GlobalCache::default(); let name = decode_name( - unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + lowlink_name.borrow(), &global_cache, ); @@ -699,7 +698,7 @@ extern "C" fn rs_get_block_bytes_len( } let global_cache = GlobalCache::default(); let name = decode_name( - unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + lowlink_name.borrow(), &global_cache, ); @@ -724,7 +723,7 @@ extern "C" fn rs_copy_block_bytes( } let global_cache = GlobalCache::default(); let name = decode_name( - unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + lowlink_name.borrow(), &global_cache, ); @@ -751,7 +750,7 @@ extern "C" fn rs_get_block_sharing_len( } let global_cache = GlobalCache::default(); let name = decode_name( - unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + lowlink_name.borrow(), &global_cache, ); @@ -877,7 +876,7 @@ extern "C" fn rs_get_pre_sharing_exprs( } let global_cache = GlobalCache::default(); let name = decode_name( - unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + lowlink_name.borrow(), &global_cache, ); @@ -982,7 +981,7 @@ extern "C" fn rs_get_pre_sharing_exprs_len( } let global_cache = GlobalCache::default(); let name = decode_name( - unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, + lowlink_name.borrow(), &global_cache, ); @@ -1047,7 +1046,7 @@ extern "C" fn rs_lookup_const_addr( } let global_cache = GlobalCache::default(); let name = decode_name( - unsafe { LeanBorrowed::from_raw(name_ptr.as_raw()) }, + name_ptr.borrow(), &global_cache, ); diff --git a/src/ffi/graph.rs b/src/ffi/graph.rs index c3ddf11d..c8346eac 100644 --- a/src/ffi/graph.rs +++ b/src/ffi/graph.rs @@ -2,7 +2,6 @@ use std::sync::Arc; -use crate::ffi::ffi_io_guard; use crate::ix::condense::compute_sccs; use crate::ix::graph::build_ref_graph; use crate::lean::LeanIxCondensedBlocks; @@ -102,14 +101,12 @@ impl LeanIxCondensedBlocks { pub extern "C" fn rs_build_ref_graph( env_consts_ptr: LeanList>, ) -> LeanIOResult { - ffi_io_guard(std::panic::AssertUnwindSafe(|| { - let rust_env = decode_env(env_consts_ptr); - let rust_env = Arc::new(rust_env); - let ref_graph = build_ref_graph(&rust_env); - let mut cache = LeanBuildCache::with_capacity(rust_env.len()); - let result = build_ref_graph_array(&mut cache, &ref_graph.out_refs); - LeanIOResult::ok(result) - })) + let rust_env = decode_env(env_consts_ptr); + let rust_env = Arc::new(rust_env); + let ref_graph = build_ref_graph(&rust_env); + let mut cache = LeanBuildCache::with_capacity(rust_env.len()); + let result = build_ref_graph_array(&mut cache, &ref_graph.out_refs); + LeanIOResult::ok(result) } /// FFI function to compute SCCs from a Lean environment. @@ -117,13 +114,11 @@ pub extern "C" fn rs_build_ref_graph( pub extern "C" fn rs_compute_sccs( env_consts_ptr: LeanList>, ) -> LeanIOResult { - ffi_io_guard(std::panic::AssertUnwindSafe(|| { - let rust_env = decode_env(env_consts_ptr); - let rust_env = Arc::new(rust_env); - let ref_graph = build_ref_graph(&rust_env); - let condensed = compute_sccs(&ref_graph.out_refs); - let mut cache = LeanBuildCache::with_capacity(rust_env.len()); - let result = LeanIxCondensedBlocks::build(&mut cache, &condensed); - LeanIOResult::ok(result) - })) + let rust_env = decode_env(env_consts_ptr); + let rust_env = Arc::new(rust_env); + let ref_graph = build_ref_graph(&rust_env); + let condensed = compute_sccs(&ref_graph.out_refs); + let mut cache = LeanBuildCache::with_capacity(rust_env.len()); + let result = LeanIxCondensedBlocks::build(&mut cache, &condensed); + LeanIOResult::ok(result) } diff --git a/src/ffi/ix/env.rs b/src/ffi/ix/env.rs index ceea7709..5e3d71a8 100644 --- a/src/ffi/ix/env.rs +++ b/src/ffi/ix/env.rs @@ -80,23 +80,17 @@ where let mut result = Vec::new(); let mut current = obj; - loop { - if current.is_scalar() { - break; - } - + while !current.is_scalar() { let ctor = current.as_ctor(); if ctor.tag() == 0 { - // AssocList.nil - break; + break; // AssocList.nil } - // AssocList.cons: 3 fields (key, value, tail) - result.push((decode_key(ctor.get(0)), decode_val(ctor.get(1)))); - // Break the borrow chain: ctor borrows from current, so we can't - // reassign current while ctor is alive. The underlying Lean objects - // outlive this loop, so this is safe. - current = unsafe { LeanBorrowed::from_raw(ctor.get(2).as_raw()) }; + let key = ctor.get(0); + let val = ctor.get(1); + let next = ctor.get(2).as_raw(); + result.push((decode_key(key), decode_val(val))); + current = unsafe { LeanBorrowed::from_raw(next) }; } result @@ -180,38 +174,36 @@ impl LeanIxRawEnvironment { } impl LeanIxRawEnvironment { + /// RawEnvironment is a single-field struct, unboxed to just Array by Lean. + fn as_array(&self) -> LeanArray> { + unsafe { LeanBorrowed::from_raw(self.as_raw()) }.as_array() + } + /// Decode Ix.RawEnvironment from Lean object into HashMap. /// RawEnvironment = { consts : Array (Name × ConstantInfo) } /// NOTE: Unboxed to just Array. This version deduplicates by name. pub fn decode(&self) -> FxHashMap { - let borrowed = unsafe { LeanBorrowed::from_raw(self.as_raw()) }; - let arr = borrowed.as_array(); + let arr = self.as_array(); let mut consts: FxHashMap = FxHashMap::default(); - for pair_obj in arr.iter() { let pair = pair_obj.as_ctor(); let name = LeanIxName(pair.get(0)).decode(); let info = LeanIxConstantInfo(pair.get(1)).decode(); consts.insert(name, info); } - consts } - /// Decode Ix.RawEnvironment from Lean object preserving array structure. - /// This version preserves all entries including duplicates. + /// Decode Ix.RawEnvironment preserving array structure (including duplicates). pub fn decode_to_vec(&self) -> Vec<(Name, ConstantInfo)> { - let borrowed = unsafe { LeanBorrowed::from_raw(self.as_raw()) }; - let arr = borrowed.as_array(); + let arr = self.as_array(); let mut consts = Vec::with_capacity(arr.len()); - for pair_obj in arr.iter() { let pair = pair_obj.as_ctor(); let name = LeanIxName(pair.get(0)).decode(); let info = LeanIxConstantInfo(pair.get(1)).decode(); consts.push((name, info)); } - consts } } @@ -226,7 +218,6 @@ impl LeanIxEnvironment { /// NOTE: Environment with a single field is UNBOXED by Lean, /// so the pointer IS the HashMap directly, not a structure containing it. pub fn decode(&self) -> FxHashMap { - // Environment is unboxed - obj IS the HashMap directly let borrowed = unsafe { LeanBorrowed::from_raw(self.as_raw()) }; let consts_pairs = decode_hashmap( borrowed, diff --git a/src/ffi/ixon/compare.rs b/src/ffi/ixon/compare.rs index 6f613d72..8bf56fcb 100644 --- a/src/ffi/ixon/compare.rs +++ b/src/ffi/ixon/compare.rs @@ -8,7 +8,7 @@ use crate::ix::ixon::serialize::put_expr; use crate::ix::mutual::MutCtx; use crate::lean::{LeanIxBlockCompareDetail, LeanIxBlockCompareResult}; use lean_ffi::object::{ - LeanBorrowed, LeanByteArray, LeanCtor, LeanList, LeanOwned, LeanRef, + LeanBorrowed, LeanByteArray, LeanCtor, LeanList, LeanOwned, }; use crate::ffi::lean_env::{ @@ -30,10 +30,7 @@ pub extern "C" fn rs_compare_expr_compilation( // Decode Lean.Expr to Rust's representation let global_cache = GlobalCache::default(); let mut cache = LeanCache::new(&global_cache); - let lean_expr = decode_expr( - unsafe { LeanBorrowed::from_raw(lean_expr_ptr.as_raw()) }, - &mut cache, - ); + let lean_expr = decode_expr(lean_expr_ptr.borrow(), &mut cache); // Create universe params for de Bruijn indexing (u0, u1, u2, ...) let univ_params: Vec = (0..univ_ctx_size) @@ -119,10 +116,7 @@ pub unsafe extern "C" fn rs_compare_block_v2( lean_sharing_len: u64, ) -> LeanIxBlockCompareDetail { let global_cache = GlobalCache::default(); - let name = decode_name( - unsafe { LeanBorrowed::from_raw(lowlink_name.as_raw()) }, - &global_cache, - ); + let name = decode_name(lowlink_name.borrow(), &global_cache); let rust_env = unsafe { &*rust_env }; let lean_data = lean_bytes.as_bytes(); diff --git a/src/ffi/lean_env.rs b/src/ffi/lean_env.rs index b373838d..a5ba4322 100644 --- a/src/ffi/lean_env.rs +++ b/src/ffi/lean_env.rs @@ -80,8 +80,21 @@ impl<'g> Cache<'g> { } } -fn collect_list_objs(list: LeanList>) -> Vec { - list.iter().map(|b| LeanShared::new(b.to_owned_ref())).collect() +/// Collect list elements as borrowed pointers (no refcount changes). +/// Uses `LeanList::to_vec` which preserves the `'a` lifetime from the +/// underlying Lean objects rather than tying it to a local borrow. +fn collect_list_borrowed<'a>(list: LeanList>) -> Vec> { + list.to_vec() +} + +/// Collect list elements as LeanShared handles for cross-thread use. +/// The caller should have already MT-marked the parent list via `LeanShared::new`, +/// so `lean_mark_mt` on each element is a single `lean_is_st` check (fast no-op). +fn collect_list_shared(list: LeanList>) -> Vec { + list + .iter() + .map(|b| LeanShared::new(b.to_owned_ref())) + .collect() } // Name decoding with global cache @@ -231,9 +244,9 @@ fn decode_syntax(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Syntax { let info = decode_source_info(info); let raw_val = decode_substring(raw_val); let val = decode_name(val, cache.global); - let preresolved = collect_list_objs(preresolved.as_list()) + let preresolved = collect_list_borrowed(preresolved.as_list()) .into_iter() - .map(|o| decode_syntax_preresolved(o.borrow(), cache)) + .map(|o| decode_syntax_preresolved(o, cache)) .collect(); Syntax::Ident(info, raw_val, val, preresolved) }, @@ -301,9 +314,9 @@ pub fn decode_expr(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Expr { 4 => { let [name_obj, levels, _hash] = ctor.objs(); let name = decode_name(name_obj, cache.global); - let levels = collect_list_objs(levels.as_list()) + let levels = collect_list_borrowed(levels.as_list()) .into_iter() - .map(|o| decode_level(o.borrow(), cache)) + .map(|o| decode_level(o, cache)) .collect(); Expr::cnst(name, levels) }, @@ -362,9 +375,9 @@ pub fn decode_expr(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Expr { }, 10 => { let [data, expr_obj] = ctor.objs(); - let kv_map: Vec<_> = collect_list_objs(data.as_list()) + let kv_map: Vec<_> = collect_list_borrowed(data.as_list()) .into_iter() - .map(|o| decode_name_data_value(o.borrow(), cache)) + .map(|o| decode_name_data_value(o, cache)) .collect(); let expr = decode_expr(expr_obj, cache); Expr::mdata(kv_map, expr) @@ -401,9 +414,9 @@ fn decode_constant_val( let ctor = obj.as_ctor(); let [name_obj, level_params, typ] = ctor.objs(); let name = decode_name(name_obj, cache.global); - let level_params: Vec<_> = collect_list_objs(level_params.as_list()) + let level_params: Vec<_> = collect_list_borrowed(level_params.as_list()) .into_iter() - .map(|o| decode_name(o.borrow(), cache.global)) + .map(|o| decode_name(o, cache.global)) .collect(); let typ = decode_expr(typ, cache); ConstantVal { name, level_params, typ } @@ -439,9 +452,9 @@ pub fn decode_constant_info( let [height] = hints_ctor.objs::<1>(); ReducibilityHints::Regular(height.as_raw() as u32) }; - let all: Vec<_> = collect_list_objs(all.as_list()) + let all: Vec<_> = collect_list_borrowed(all.as_list()) .into_iter() - .map(|o| decode_name(o.borrow(), cache.global)) + .map(|o| decode_name(o, cache.global)) .collect(); let safety = match safety.as_raw() as usize { 0 => DefinitionSafety::Unsafe, @@ -461,9 +474,9 @@ pub fn decode_constant_info( let [constant_val, value, all] = inner.objs(); let constant_val = decode_constant_val(constant_val, cache); let value = decode_expr(value, cache); - let all: Vec<_> = collect_list_objs(all.as_list()) + let all: Vec<_> = collect_list_borrowed(all.as_list()) .into_iter() - .map(|o| decode_name(o.borrow(), cache.global)) + .map(|o| decode_name(o, cache.global)) .collect(); ConstantInfo::ThmInfo(TheoremVal { cnst: constant_val, value, all }) }, @@ -471,9 +484,9 @@ pub fn decode_constant_info( let [constant_val, value, all, is_unsafe] = inner.objs(); let constant_val = decode_constant_val(constant_val, cache); let value = decode_expr(value, cache); - let all: Vec<_> = collect_list_objs(all.as_list()) + let all: Vec<_> = collect_list_borrowed(all.as_list()) .into_iter() - .map(|o| decode_name(o.borrow(), cache.global)) + .map(|o| decode_name(o, cache.global)) .collect(); let is_unsafe = is_unsafe.as_raw() as usize == 1; ConstantInfo::OpaqueInfo(OpaqueVal { @@ -508,13 +521,13 @@ pub fn decode_constant_info( let constant_val = decode_constant_val(constant_val, cache); let num_params = Nat::from_obj(&num_params); let num_indices = Nat::from_obj(&num_indices); - let all: Vec<_> = collect_list_objs(all.as_list()) + let all: Vec<_> = collect_list_borrowed(all.as_list()) .into_iter() - .map(|o| decode_name(o.borrow(), cache.global)) + .map(|o| decode_name(o, cache.global)) .collect(); - let ctors: Vec<_> = collect_list_objs(ctors.as_list()) + let ctors: Vec<_> = collect_list_borrowed(ctors.as_list()) .into_iter() - .map(|o| decode_name(o.borrow(), cache.global)) + .map(|o| decode_name(o, cache.global)) .collect(); let num_nested = Nat::from_obj(&num_nested); let [is_rec, is_unsafe, is_reflexive, ..] = @@ -561,17 +574,17 @@ pub fn decode_constant_info( bools, ] = inner.objs(); let constant_val = decode_constant_val(constant_val, cache); - let all: Vec<_> = collect_list_objs(all.as_list()) + let all: Vec<_> = collect_list_borrowed(all.as_list()) .into_iter() - .map(|o| decode_name(o.borrow(), cache.global)) + .map(|o| decode_name(o, cache.global)) .collect(); let num_params = Nat::from_obj(&num_params); let num_indices = Nat::from_obj(&num_indices); let num_motives = Nat::from_obj(&num_motives); let num_minors = Nat::from_obj(&num_minors); - let rules: Vec<_> = collect_list_objs(rules.as_list()) + let rules: Vec<_> = collect_list_borrowed(rules.as_list()) .into_iter() - .map(|o| decode_recursor_rule(o.borrow(), cache)) + .map(|o| decode_recursor_rule(o, cache)) .collect(); let [k, is_unsafe, ..] = (bools.as_raw() as usize).to_le_bytes().map(|b| b == 1); @@ -606,17 +619,15 @@ fn decode_name_constant_info( // Decode a Lean environment in parallel with hybrid caching. pub fn decode_env(list: LeanList>) -> Env { - // Phase 1: Mark entire list graph as MT and collect elements as shared + // Phase 1: Mark entire list graph as MT, then collect elements as LeanShared. + // lean_mark_mt recursively marks all reachable objects. Subsequent + // LeanShared::new calls on elements are a fast no-op (single is_st check). let shared_list = LeanShared::new(list.inner().to_owned_ref()); - let objs: Vec = shared_list - .borrow() - .as_list() - .iter() - .map(|b| LeanShared::new(b.to_owned_ref())) - .collect(); + let objs = collect_list_shared(shared_list.borrow().as_list()); if objs.len() < PARALLEL_THRESHOLD { - // Sequential fallback for small environments + // Sequential fallback for small environments — no MT overhead needed, + // but objects are already marked. Just borrow directly. let global = GlobalCache::new(); let mut env = Env::default(); env.reserve(objs.len()); diff --git a/src/ffi/primitives.rs b/src/ffi/primitives.rs index 5f6088b5..62d2ae17 100644 --- a/src/ffi/primitives.rs +++ b/src/ffi/primitives.rs @@ -23,7 +23,7 @@ pub extern "C" fn rs_roundtrip_assoclist_nat_nat( if list_ptr.is_scalar() { return LeanOwned::box_usize(0); } - let pairs = decode_assoc_list_nat_nat(&list_ptr); + let pairs = decode_assoc_list_nat_nat(list_ptr); build_assoc_list_nat_nat(&pairs) } @@ -61,7 +61,7 @@ pub extern "C" fn rs_roundtrip_dhashmap_raw_nat_nat( let mut all_pairs: Vec<(Nat, Nat)> = Vec::new(); for bucket in buckets.iter() { - let pairs = decode_assoc_list_nat_nat(&bucket); + let pairs = decode_assoc_list_nat_nat(bucket); all_pairs.extend(pairs); } @@ -126,7 +126,7 @@ pub extern "C" fn rs_roundtrip_hashmap_nat_nat( let mut pairs: Vec<(Nat, Nat)> = Vec::new(); for bucket in buckets.iter() { - let bucket_pairs = decode_assoc_list_nat_nat(&bucket); + let bucket_pairs = decode_assoc_list_nat_nat(bucket); pairs.extend(bucket_pairs); } @@ -176,26 +176,20 @@ pub extern "C" fn rs_roundtrip_hashmap_nat_nat( /// Decode a Lean AssocList Nat Nat to Vec of pairs /// AssocList: nil (tag 0) | cons key value tail (tag 1, 3 fields) -pub fn decode_assoc_list_nat_nat(obj: &impl LeanRef) -> Vec<(Nat, Nat)> { +pub fn decode_assoc_list_nat_nat(obj: LeanBorrowed<'_>) -> Vec<(Nat, Nat)> { let mut result = Vec::new(); - let mut current_ptr = obj.as_raw(); + let mut current = obj; - loop { - if current_ptr as usize & 1 == 1 { - break; - } - - let current = unsafe { LeanBorrowed::from_raw(current_ptr) }; + while !current.is_scalar() { let ctor = current.as_ctor(); if ctor.tag() == 0 { break; } - - let k = Nat::from_obj(&ctor.get(0)); - let v = Nat::from_obj(&ctor.get(1)); - - result.push((k, v)); - current_ptr = ctor.get(2).as_raw(); + let key = ctor.get(0); + let val = ctor.get(1); + let next = ctor.get(2).as_raw(); + result.push((Nat::from_obj(&key), Nat::from_obj(&val))); + current = unsafe { LeanBorrowed::from_raw(next) }; } result diff --git a/src/ffi/refcount.rs b/src/ffi/refcount.rs index 476b1782..f5edd77c 100644 --- a/src/ffi/refcount.rs +++ b/src/ffi/refcount.rs @@ -11,6 +11,7 @@ //! - Nested field access from borrowed refs //! - Cache deduplication via Clone //! - Repeated alloc/dealloc cycles +//! - Multi-threaded access via LeanShared use std::thread; From 0e9e17dd3f31f5b48f727b4e57ae6292db3f6700 Mon Sep 17 00:00:00 2001 From: samuelburnham <45365069+samuelburnham@users.noreply.github.com> Date: Fri, 20 Mar 2026 12:03:27 -0400 Subject: [PATCH 3/7] fmt --- src/ffi.rs | 4 +++- src/ffi/compile.rs | 47 +++++++++++----------------------------- src/ffi/ix/constant.rs | 2 +- src/ffi/ix/data.rs | 4 +--- src/ffi/ix/expr.rs | 4 +--- src/ffi/ixon/constant.rs | 2 +- src/ffi/ixon/enums.rs | 2 +- src/ffi/lean_env.rs | 13 +++++------ src/ffi/primitives.rs | 2 +- 9 files changed, 28 insertions(+), 52 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index bed68a8e..c975f8bd 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -27,7 +27,9 @@ pub mod primitives; // Primitives: rs_roundtrip_nat, rs_roundtrip_string, etc. pub mod refcount; // Reference counting / ownership tests (test-only) #[cfg(feature = "test-ffi")] -use lean_ffi::object::{LeanArray, LeanBorrowed, LeanByteArray, LeanOwned, LeanRef}; +use lean_ffi::object::{ + LeanArray, LeanBorrowed, LeanByteArray, LeanOwned, LeanRef, +}; #[cfg(feature = "test-ffi")] #[unsafe(no_mangle)] diff --git a/src/ffi/compile.rs b/src/ffi/compile.rs index 425a9bf8..0e240324 100644 --- a/src/ffi/compile.rs +++ b/src/ffi/compile.rs @@ -16,16 +16,16 @@ use crate::ix::decompile::decompile_env; use crate::ix::env::Name; use crate::ix::graph::build_ref_graph; use crate::ix::ixon::constant::Constant as IxonConstant; -use crate::ix::ixon::{Comm, ConstantMeta}; #[cfg(feature = "test-ffi")] use crate::ix::ixon::constant::ConstantInfo; #[cfg(feature = "test-ffi")] use crate::ix::ixon::expr::Expr as IxonExpr; +use crate::ix::ixon::{Comm, ConstantMeta}; use crate::lean::{ LeanIxCompileError, LeanIxCondensedBlocks, LeanIxConstantInfo, - LeanIxDecompileError, LeanIxName, LeanIxRawEnvironment, - LeanIxSerializeError, LeanIxonRawBlob, LeanIxonRawComm, LeanIxonRawConst, - LeanIxonRawEnv, LeanIxonRawNameEntry, LeanIxonRawNamed, + LeanIxDecompileError, LeanIxName, LeanIxRawEnvironment, LeanIxSerializeError, + LeanIxonRawBlob, LeanIxonRawComm, LeanIxonRawConst, LeanIxonRawEnv, + LeanIxonRawNameEntry, LeanIxonRawNamed, }; use lean_ffi::nat::Nat; use lean_ffi::object::LeanIOResult; @@ -43,7 +43,7 @@ use crate::ffi::lean_env::decode_env; use crate::lean::LeanIxAddress; #[cfg(feature = "test-ffi")] -use std::collections::HashMap; +use crate::ffi::lean_env::{GlobalCache, decode_name}; #[cfg(feature = "test-ffi")] use crate::ix::ixon::serialize::put_expr; #[cfg(feature = "test-ffi")] @@ -51,7 +51,7 @@ use crate::lean::{ LeanIxBlockCompareDetail, LeanIxBlockCompareResult, LeanIxCompilePhases, }; #[cfg(feature = "test-ffi")] -use crate::ffi::lean_env::{GlobalCache, decode_name}; +use std::collections::HashMap; // ============================================================================= // Helper builders @@ -629,10 +629,7 @@ extern "C" fn rs_compare_block( return 2u64 << 32; // not found } let global_cache = GlobalCache::default(); - let name = decode_name( - lowlink_name.borrow(), - &global_cache, - ); + let name = decode_name(lowlink_name.borrow(), &global_cache); let rust_env = unsafe { &*rust_env }; let lean_data = lean_bytes.as_bytes(); @@ -697,10 +694,7 @@ extern "C" fn rs_get_block_bytes_len( return 0; } let global_cache = GlobalCache::default(); - let name = decode_name( - lowlink_name.borrow(), - &global_cache, - ); + let name = decode_name(lowlink_name.borrow(), &global_cache); let rust_env = unsafe { &*rust_env }; @@ -722,10 +716,7 @@ extern "C" fn rs_copy_block_bytes( return; } let global_cache = GlobalCache::default(); - let name = decode_name( - lowlink_name.borrow(), - &global_cache, - ); + let name = decode_name(lowlink_name.borrow(), &global_cache); let rust_env = unsafe { &*rust_env }; @@ -749,10 +740,7 @@ extern "C" fn rs_get_block_sharing_len( return 0; } let global_cache = GlobalCache::default(); - let name = decode_name( - lowlink_name.borrow(), - &global_cache, - ); + let name = decode_name(lowlink_name.borrow(), &global_cache); let rust_env = unsafe { &*rust_env }; @@ -875,10 +863,7 @@ extern "C" fn rs_get_pre_sharing_exprs( return 0; } let global_cache = GlobalCache::default(); - let name = decode_name( - lowlink_name.borrow(), - &global_cache, - ); + let name = decode_name(lowlink_name.borrow(), &global_cache); let rust_env = unsafe { &*rust_env }; @@ -980,10 +965,7 @@ extern "C" fn rs_get_pre_sharing_exprs_len( return 0; } let global_cache = GlobalCache::default(); - let name = decode_name( - lowlink_name.borrow(), - &global_cache, - ); + let name = decode_name(lowlink_name.borrow(), &global_cache); let rust_env = unsafe { &*rust_env }; @@ -1045,10 +1027,7 @@ extern "C" fn rs_lookup_const_addr( return 0; } let global_cache = GlobalCache::default(); - let name = decode_name( - name_ptr.borrow(), - &global_cache, - ); + let name = decode_name(name_ptr.borrow(), &global_cache); let rust_env = unsafe { &*rust_env }; diff --git a/src/ffi/ix/constant.rs b/src/ffi/ix/constant.rs index ca1758b2..99473c29 100644 --- a/src/ffi/ix/constant.rs +++ b/src/ffi/ix/constant.rs @@ -20,9 +20,9 @@ use crate::lean::{ LeanIxRecursorRule, LeanIxReducibilityHints, }; use lean_ffi::nat::Nat; -use lean_ffi::object::{LeanArray, LeanCtor, LeanOwned, LeanRef}; #[cfg(feature = "test-ffi")] use lean_ffi::object::LeanBorrowed; +use lean_ffi::object::{LeanArray, LeanCtor, LeanOwned, LeanRef}; use crate::ffi::builder::LeanBuildCache; diff --git a/src/ffi/ix/data.rs b/src/ffi/ix/data.rs index 42ff0a2a..647a7e2e 100644 --- a/src/ffi/ix/data.rs +++ b/src/ffi/ix/data.rs @@ -8,11 +8,9 @@ use crate::lean::{ LeanIxSyntax, LeanIxSyntaxPreresolved, }; use lean_ffi::nat::Nat; -use lean_ffi::object::{ - LeanArray, LeanCtor, LeanOwned, LeanRef, LeanString, -}; #[cfg(feature = "test-ffi")] use lean_ffi::object::LeanBorrowed; +use lean_ffi::object::{LeanArray, LeanCtor, LeanOwned, LeanRef, LeanString}; use crate::ffi::builder::LeanBuildCache; diff --git a/src/ffi/ix/expr.rs b/src/ffi/ix/expr.rs index 62ea5f16..e00deb51 100644 --- a/src/ffi/ix/expr.rs +++ b/src/ffi/ix/expr.rs @@ -24,11 +24,9 @@ use crate::lean::{ LeanIxName, }; use lean_ffi::nat::Nat; -use lean_ffi::object::{ - LeanCtor, LeanOwned, LeanRef, LeanString, -}; #[cfg(feature = "test-ffi")] use lean_ffi::object::LeanBorrowed; +use lean_ffi::object::{LeanCtor, LeanOwned, LeanRef, LeanString}; impl LeanIxExpr { /// Build a Lean Ix.Expr with embedded hash. diff --git a/src/ffi/ixon/constant.rs b/src/ffi/ixon/constant.rs index 140ba968..2a859393 100644 --- a/src/ffi/ixon/constant.rs +++ b/src/ffi/ixon/constant.rs @@ -21,9 +21,9 @@ use crate::lean::{ LeanIxonInductiveProj, LeanIxonMutConst, LeanIxonQuotient, LeanIxonRecursor, LeanIxonRecursorProj, LeanIxonRecursorRule, LeanIxonUniv, }; -use lean_ffi::object::{LeanArray, LeanCtor, LeanOwned, LeanRef}; #[cfg(feature = "test-ffi")] use lean_ffi::object::LeanBorrowed; +use lean_ffi::object::{LeanArray, LeanCtor, LeanOwned, LeanRef}; // ============================================================================= // Definition diff --git a/src/ffi/ixon/enums.rs b/src/ffi/ixon/enums.rs index 3a3d0721..0afa0593 100644 --- a/src/ffi/ixon/enums.rs +++ b/src/ffi/ixon/enums.rs @@ -5,9 +5,9 @@ use crate::ix::ixon::constant::DefKind; use crate::lean::{ LeanIxonDefKind, LeanIxonDefinitionSafety, LeanIxonQuotKind, }; -use lean_ffi::object::{LeanOwned, LeanRef}; #[cfg(feature = "test-ffi")] use lean_ffi::object::LeanBorrowed; +use lean_ffi::object::{LeanOwned, LeanRef}; impl LeanIxonDefKind { /// Build Ixon.DefKind diff --git a/src/ffi/lean_env.rs b/src/ffi/lean_env.rs index a5ba4322..4919606b 100644 --- a/src/ffi/lean_env.rs +++ b/src/ffi/lean_env.rs @@ -17,12 +17,12 @@ use rayon::prelude::*; use rustc_hash::FxHashMap; -#[cfg(feature = "test-ffi")] -use std::sync::Arc; #[cfg(feature = "test-ffi")] use crate::ix::compile::compile_env; #[cfg(feature = "test-ffi")] use crate::ix::decompile::{check_decompile, decompile_env}; +#[cfg(feature = "test-ffi")] +use std::sync::Arc; use lean_ffi::nat::Nat; use lean_ffi::object::{LeanBorrowed, LeanList, LeanRef, LeanShared}; @@ -83,7 +83,9 @@ impl<'g> Cache<'g> { /// Collect list elements as borrowed pointers (no refcount changes). /// Uses `LeanList::to_vec` which preserves the `'a` lifetime from the /// underlying Lean objects rather than tying it to a local borrow. -fn collect_list_borrowed<'a>(list: LeanList>) -> Vec> { +fn collect_list_borrowed<'a>( + list: LeanList>, +) -> Vec> { list.to_vec() } @@ -91,10 +93,7 @@ fn collect_list_borrowed<'a>(list: LeanList>) -> Vec>) -> Vec { - list - .iter() - .map(|b| LeanShared::new(b.to_owned_ref())) - .collect() + list.iter().map(|b| LeanShared::new(b.to_owned_ref())).collect() } // Name decoding with global cache diff --git a/src/ffi/primitives.rs b/src/ffi/primitives.rs index 62d2ae17..c73e1448 100644 --- a/src/ffi/primitives.rs +++ b/src/ffi/primitives.rs @@ -4,9 +4,9 @@ //! (ix-specific types not covered by the lean-ffi test suite). use lean_ffi::nat::Nat; -use lean_ffi::object::{LeanBorrowed, LeanRef}; #[cfg(feature = "test-ffi")] use lean_ffi::object::{LeanArray, LeanCtor, LeanOwned}; +use lean_ffi::object::{LeanBorrowed, LeanRef}; // ============================================================================= // AssocList / HashMap roundtrip FFI functions From 5eab31b16411220799cb7d1786c9452e749b3f28 Mon Sep 17 00:00:00 2001 From: samuelburnham <45365069+samuelburnham@users.noreply.github.com> Date: Fri, 20 Mar 2026 12:20:15 -0400 Subject: [PATCH 4/7] Fix Nix build --- flake.nix | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/flake.nix b/flake.nix index 64fed5a3..2f357673 100644 --- a/flake.nix +++ b/flake.nix @@ -87,9 +87,19 @@ }; cargoArtifacts = craneLib.buildDepsOnly craneArgs; + # Production build: parallel + test-ffi (test-ffi is included because + # the same static lib is linked into both the Ix library and the test binary) rustPkg = craneLib.buildPackage (craneArgs // { inherit cargoArtifacts; + cargoExtraArgs = "--locked --features parallel,test-ffi"; + }); + + # Release build without test-ffi (for distribution) + rustPkgRelease = craneLib.buildPackage (craneArgs + // { + inherit cargoArtifacts; + cargoExtraArgs = "--locked --features parallel"; }); # Lake package @@ -100,21 +110,27 @@ Blake3 = blake3-lean.packages.${system}.default; }; }; - lakeBuildArgs = { + # Shared Lake build args: patches out the Cargo build (Crane handles it) + mkLakeBuildArgs = rustLib: { inherit lakeDeps; src = ./.; # Don't build the `ix_rs` static lib with Lake, since we build it with Crane postPatch = '' - substituteInPlace lakefile.lean --replace-fail "let args := match (ixNoPar, ixNet)" "let _args := match (ixNoPar, ixNet)" substituteInPlace lakefile.lean --replace-fail 'proc { cmd := "cargo"' '--proc { cmd := "cargo"' ''; - # Copy the `ix_rs` static lib from Crane to `target/release` so Lake can use it + # Symlink the Crane-built static lib to where Lake expects it postConfigure = '' mkdir -p target/release - ln -s ${rustPkg}/lib/libix_rs.a target/release/ + ln -s ${rustLib}/lib/libix_rs.a target/release/ ''; buildInputs = [pkgs.gmp pkgs.lean.lean-all pkgs.rsync]; }; + + # Release build args (no test-ffi symbols) + lakeBuildArgs = mkLakeBuildArgs rustPkgRelease; + # Test build args (includes test-ffi symbols) + lakeTestBuildArgs = mkLakeBuildArgs rustPkg; + ixLib = lake2nix.mkPackage (lakeBuildArgs // { name = "Ix"; @@ -124,7 +140,8 @@ lakeBuildArgs // { lakeArtifacts = ixLib; - installArtifacts = false; + # Binaries that import Ix.Meta need .olean files at runtime via LEAN_PATH + installArtifacts = true; }; leanPath = pkgs.lib.concatStringsSep ":" ( map (d: "${d}/.lake/build/lib/lean") ([ixLib] ++ builtins.attrValues lakeDeps) @@ -140,7 +157,8 @@ done ''; ixCLI = wrapBin (lake2nix.mkPackage (lakeBinArgs // {name = "ix";})); - ixTest = wrapBin (lake2nix.mkPackage (lakeBinArgs + # Test binary links rustPkg (with test-ffi) instead of rustPkgRelease + ixTest = wrapBin (lake2nix.mkPackage (lakeTestBuildArgs // { name = "IxTests"; installArtifacts = true; From a15333b4ca9dbd40ea823f689d6035f57636e7f1 Mon Sep 17 00:00:00 2001 From: samuelburnham <45365069+samuelburnham@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:53:49 -0400 Subject: [PATCH 5/7] Update lean-ffi --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b31e821..98d297c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1846,7 +1846,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lean-ffi" version = "0.1.0" -source = "git+https://github.com/argumentcomputer/lean-ffi?rev=ffbbc0882afa1162c0806c8a5c63ea5da631dd6c#ffbbc0882afa1162c0806c8a5c63ea5da631dd6c" +source = "git+https://github.com/argumentcomputer/lean-ffi?rev=a94c426f0ce0b13ffdf7940e3e6368560628f2c9#a94c426f0ce0b13ffdf7940e3e6368560628f2c9" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml index df2b2dda..84b7dea1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ anyhow = "1" blake3 = "1.8.2" itertools = "0.14.0" indexmap = { version = "2", features = ["rayon"] } -lean-ffi = { git = "https://github.com/argumentcomputer/lean-ffi", rev = "ffbbc0882afa1162c0806c8a5c63ea5da631dd6c" } +lean-ffi = { git = "https://github.com/argumentcomputer/lean-ffi", rev = "a94c426f0ce0b13ffdf7940e3e6368560628f2c9" } multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "bdb0d7d66c02b554e66c449da2dbf12dc0dc27af" } num-bigint = "0.4.6" rayon = "1" From 3a392d0c897ac9f22798c7ae921bda8b864862d8 Mon Sep 17 00:00:00 2001 From: samuelburnham <45365069+samuelburnham@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:18:44 -0400 Subject: [PATCH 6/7] Fix lakefile Rust config --- Tests/FFI/Refcount.lean | 2 +- flake.nix | 3 +- lakefile.lean | 61 +++++++++++++++++++++++++++++++---------- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/Tests/FFI/Refcount.lean b/Tests/FFI/Refcount.lean index 0ad4fe3f..c132a1f2 100644 --- a/Tests/FFI/Refcount.lean +++ b/Tests/FFI/Refcount.lean @@ -1,7 +1,7 @@ /- Reference counting and ownership tests for the typed lean-ffi API. Gated behind the `test-ffi` Cargo feature (included by default, - stripped with `IX_RELEASE=1`). + stripped in non-test builds; included via the `ix_rs_test` Lake target). These tests exercise ownership semantics that catch double-free, use-after-free, and refcount leaks by calling Rust FFI functions that: diff --git a/flake.nix b/flake.nix index 2f357673..da92c19e 100644 --- a/flake.nix +++ b/flake.nix @@ -87,8 +87,7 @@ }; cargoArtifacts = craneLib.buildDepsOnly craneArgs; - # Production build: parallel + test-ffi (test-ffi is included because - # the same static lib is linked into both the Ix library and the test binary) + # Test build: parallel + test-ffi (only used by ixTest) rustPkg = craneLib.buildPackage (craneArgs // { inherit cargoArtifacts; diff --git a/lakefile.lean b/lakefile.lean index 7cbac727..1d45adfd 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -3,9 +3,11 @@ open System Lake DSL package ix where version := v!"0.1.0" + moreLinkArgs := #["target/release/" ++ nameToStaticLib "ix_rs"] @[default_target] -lean_lib Ix +lean_lib Ix where + needs := #[`@/ix_rs] lean_exe ix where root := `Main @@ -31,6 +33,7 @@ lean_lib Tests lean_exe IxTests where root := `Tests.Main supportInterpreter := true + needs := #[`@/ix_rs_test] end Tests @@ -68,28 +71,56 @@ lean_exe Apps.ZKVoting.Verifier end IxApplications +/-! ## FFI + +We use `target` rather than `extern_lib` for the Rust static lib for two reasons: + +1. **Build-time Rust compilation** `extern_lib` runs at link time (when an exe is linked), + so `lake build` on a `lean_lib` alone would not trigger it. `target` runs during + module compilation (via `needs`), so `lake build` on the default `Ix` lib is enough + to build the Rust crate. + +2. **Test-ffi toggle** `lake test` needs the `test-ffi` Cargo feature; `lake build` does not. + Two targets (`ix_rs` and `ix_rs_test`) write to the same lib path. `ix_rs_test` calls + `ix_rs.fetch` to guarantee ordering, then overwrites the lib with test-ffi symbols. + `extern_lib` would run last (at link time) and overwrite the test-ffi build instead. + +`moreLinkArgs` is then needed on the `package` to tells the linker where to find the lib, +replacing the auto-linking that `extern_lib` provides. +-/ section FFI -/-- Build the static lib for the Rust crate -/ -extern_lib ix_rs pkg := do - -- Feature flags, configured via env vars: - -- IX_NO_PAR=1 — disable parallel feature - -- IX_NET=1 — enable networking (iroh) - -- IX_RELEASE=1 — strip test-ffi code for release builds - -- Cargo output is visible with `lake -v build`. +/-- Build args for `cargo build --release` with feature flags from env vars. + Feature flags: + IX_NO_PAR=1 — disable parallel feature + IX_NET=1 — enable networking (iroh) + Cargo output is visible with `lake -v build`. -/ +def cargoArgs (testFfi : Bool := false) : IO (Array String) := do let ixNoPar ← IO.getEnv "IX_NO_PAR" let ixNet ← IO.getEnv "IX_NET" - let ixRelease ← IO.getEnv "IX_RELEASE" - let buildArgs := #["build", "--release"] let mut features : Array String := #[] if ixNoPar != some "1" then features := features.push "parallel" if ixNet == some "1" then features := features.push "net" - if ixRelease != some "1" then features := features.push "test-ffi" - let args := if features.isEmpty then buildArgs - else buildArgs ++ ["--features", ",".intercalate features.toList] + if testFfi then features := features.push "test-ffi" + let buildArgs := #["build", "--release"] + if features.isEmpty then return buildArgs + else return buildArgs ++ #["--features", ",".intercalate features.toList] + +/-- Build the Rust static lib WITHOUT the `test-ffi` feature (default for `lake build`). -/ +target ix_rs pkg : FilePath := do + let args ← cargoArgs + proc { cmd := "cargo", args, cwd := pkg.dir } (quiet := true) + inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_rs" + +/-- Rebuild the Rust static lib WITH the `test-ffi` feature. + Only triggered by `lake test` (via `needs` on `Tests`). + Fetches `ix_rs` first to guarantee it completes before we overwrite + the same lib file with the test-ffi version. -/ +target ix_rs_test pkg : FilePath := do + let _ ← ix_rs.fetch + let args ← cargoArgs (testFfi := true) proc { cmd := "cargo", args, cwd := pkg.dir } (quiet := true) - let libName := nameToStaticLib "ix_rs" - inputBinFile $ pkg.dir / "target" / "release" / libName + inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_rs" end FFI From e46b5797eceb800893528f1d4b86e173500dfbdd Mon Sep 17 00:00:00 2001 From: samuelburnham <45365069+samuelburnham@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:56:20 -0400 Subject: [PATCH 7/7] Simplify lakefile.lean --- lakefile.lean | 118 +++++++++++++++++++++++--------------------------- 1 file changed, 55 insertions(+), 63 deletions(-) diff --git a/lakefile.lean b/lakefile.lean index 1d45adfd..fe125694 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -3,15 +3,6 @@ open System Lake DSL package ix where version := v!"0.1.0" - moreLinkArgs := #["target/release/" ++ nameToStaticLib "ix_rs"] - -@[default_target] -lean_lib Ix where - needs := #[`@/ix_rs] - -lean_exe ix where - root := `Main - supportInterpreter := true require LSpec from git "https://github.com/argumentcomputer/LSpec" @ "928f27c7de8318455ba0be7461dbdf7096f4075a" @@ -25,6 +16,60 @@ require Cli from git require batteries from git "https://github.com/leanprover-community/batteries" @ "v4.28.0" +/-! ## FFI + +The Rust static lib uses `target` + `moreLinkObjs` instead of `extern_lib` because +`lake test` needs the `test-ffi` Cargo feature for Rust test-only code, +but `lake build` should not include it. The `ix_rs` and `ix_rs_test` targets both write +to the same lib path; `ix_rs_test` calls `ix_rs.fetch` to ensure `ix_rs` completes first, +then overwrites the lib with test-ffi symbols. An `extern_lib` would always run last +(at link time) and overwrite the test-ffi build. + +Note: `extern_lib` only runs at link time, so `lake build` on a `lean_lib` alone wouldn't +trigger the Cargo build. With `target` + `moreLinkObjs`, the Rust static lib is built during +module compilation on the default `Ix` lib. +-/ +section FFI + +/-- Build args for `cargo build --release` with feature flags from env vars. +Cargo output is visible with `lake -v build`. -/ +def cargoArgs (testFfi : Bool := false) : IO (Array String) := do + -- IX_NO_PAR=1 disables parallel; IX_NET=1 enables networking (iroh) + let ixNoPar ← IO.getEnv "IX_NO_PAR" + let ixNet ← IO.getEnv "IX_NET" + let mut features : Array String := #[] + if ixNoPar != some "1" then features := features.push "parallel" + if ixNet == some "1" then features := features.push "net" + if testFfi then features := features.push "test-ffi" + let buildArgs := #["build", "--release"] + if features.isEmpty then return buildArgs + else return buildArgs ++ #["--features", ",".intercalate features.toList] + +/-- Build the Rust static lib without `test-ffi` (default for `lake build`). -/ +target ix_rs pkg : FilePath := do + let args ← cargoArgs + proc { cmd := "cargo", args, cwd := pkg.dir } (quiet := true) + inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_rs" + +/-- Rebuild the Rust static lib with `test-ffi`. +Only triggered by `lake test` (via `moreLinkObjs` on `IxTests`). +Fetches `ix_rs` first to guarantee ordering before overwriting the lib. -/ +target ix_rs_test pkg : FilePath := do + let _ ← ix_rs.fetch + let args ← cargoArgs (testFfi := true) + proc { cmd := "cargo", args, cwd := pkg.dir } (quiet := true) + inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_rs" + +end FFI + +@[default_target] +lean_lib Ix where + moreLinkObjs := #[ix_rs] + +lean_exe ix where + root := `Main + supportInterpreter := true + section Tests lean_lib Tests @@ -33,7 +78,7 @@ lean_lib Tests lean_exe IxTests where root := `Tests.Main supportInterpreter := true - needs := #[`@/ix_rs_test] + moreLinkObjs := #[ix_rs_test] end Tests @@ -71,59 +116,6 @@ lean_exe Apps.ZKVoting.Verifier end IxApplications -/-! ## FFI - -We use `target` rather than `extern_lib` for the Rust static lib for two reasons: - -1. **Build-time Rust compilation** `extern_lib` runs at link time (when an exe is linked), - so `lake build` on a `lean_lib` alone would not trigger it. `target` runs during - module compilation (via `needs`), so `lake build` on the default `Ix` lib is enough - to build the Rust crate. - -2. **Test-ffi toggle** `lake test` needs the `test-ffi` Cargo feature; `lake build` does not. - Two targets (`ix_rs` and `ix_rs_test`) write to the same lib path. `ix_rs_test` calls - `ix_rs.fetch` to guarantee ordering, then overwrites the lib with test-ffi symbols. - `extern_lib` would run last (at link time) and overwrite the test-ffi build instead. - -`moreLinkArgs` is then needed on the `package` to tells the linker where to find the lib, -replacing the auto-linking that `extern_lib` provides. --/ -section FFI - -/-- Build args for `cargo build --release` with feature flags from env vars. - Feature flags: - IX_NO_PAR=1 — disable parallel feature - IX_NET=1 — enable networking (iroh) - Cargo output is visible with `lake -v build`. -/ -def cargoArgs (testFfi : Bool := false) : IO (Array String) := do - let ixNoPar ← IO.getEnv "IX_NO_PAR" - let ixNet ← IO.getEnv "IX_NET" - let mut features : Array String := #[] - if ixNoPar != some "1" then features := features.push "parallel" - if ixNet == some "1" then features := features.push "net" - if testFfi then features := features.push "test-ffi" - let buildArgs := #["build", "--release"] - if features.isEmpty then return buildArgs - else return buildArgs ++ #["--features", ",".intercalate features.toList] - -/-- Build the Rust static lib WITHOUT the `test-ffi` feature (default for `lake build`). -/ -target ix_rs pkg : FilePath := do - let args ← cargoArgs - proc { cmd := "cargo", args, cwd := pkg.dir } (quiet := true) - inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_rs" - -/-- Rebuild the Rust static lib WITH the `test-ffi` feature. - Only triggered by `lake test` (via `needs` on `Tests`). - Fetches `ix_rs` first to guarantee it completes before we overwrite - the same lib file with the test-ffi version. -/ -target ix_rs_test pkg : FilePath := do - let _ ← ix_rs.fetch - let args ← cargoArgs (testFfi := true) - proc { cmd := "cargo", args, cwd := pkg.dir } (quiet := true) - inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_rs" - -end FFI - section Scripts open IO in