@@ -22,7 +22,9 @@ use super::compiler::{LfmProgram, compile};
2222use super :: executor:: execute;
2323use super :: per_table_aggregator:: { DerivedChild , LegCells , hint_public_words, publics_arena} ;
2424use super :: per_table_aggregator_tests:: { RealChild , child_arena_words, real_child_timed} ;
25- use super :: proof:: { LfmProof , aggregation_wrap_options, lfm_prove} ;
25+ use super :: proof:: {
26+ LfmProof , aggregation_wrap_options, decide_lfm_residency, lfm_execute_and_fill, lfm_prove,
27+ } ;
2628use super :: whir_block:: {
2729 BLOCK_FAN_IN , BlockPartition , LEAF_PERMS_CAP , LeafChecks , WhirBlockPlan , artifacts_of,
2830 block_leaf_arena, emit_share, id_words, leaf_partition, leaf_program_with, out_halves,
@@ -1143,13 +1145,53 @@ struct LevelTiming {
11431145 programs : Vec < ( f64 , f64 ) > ,
11441146}
11451147
1148+ /// A value one thread publishes once and others wait for. A publisher that
1149+ /// unwinds before publishing leaves an error behind ([`PublishGuard`]), so no
1150+ /// waiter blocks on a value that will never come.
1151+ struct Published < T > ( std:: sync:: OnceLock < Result < T , String > > ) ;
1152+
1153+ impl < T > Published < T > {
1154+ fn new ( ) -> Self {
1155+ Self ( std:: sync:: OnceLock :: new ( ) )
1156+ }
1157+
1158+ fn wait ( & self ) -> Result < & T , String > {
1159+ self . 0 . wait ( ) . as_ref ( ) . map_err ( Clone :: clone)
1160+ }
1161+
1162+ fn take ( self ) -> Result < T , String > {
1163+ self . 0
1164+ . into_inner ( )
1165+ . unwrap_or_else ( || Err ( "never published" . to_string ( ) ) )
1166+ }
1167+ }
1168+
1169+ /// Publishes an error on drop unless [`PublishGuard::publish`] ran first.
1170+ struct PublishGuard < ' a , T > ( & ' a Published < T > ) ;
1171+
1172+ impl < T > PublishGuard < ' _ , T > {
1173+ fn publish ( self , value : Result < T , String > ) {
1174+ let _ = self . 0 . 0 . set ( value) ;
1175+ }
1176+ }
1177+
1178+ impl < T > Drop for PublishGuard < ' _ , T > {
1179+ fn drop ( & mut self ) {
1180+ let _ = self . 0 . 0 . set ( Err ( "the publisher unwound" . to_string ( ) ) ) ;
1181+ }
1182+ }
1183+
11461184/// The tree proved the way a prover would run it, with the leaves' programs
11471185/// emitted beforehand (`leaves`, while phase B ran):
1148- /// 1. the leaves' artifacts, in parallel;
1186+ /// 1. the leaves' artifacts, in parallel, on a thread of their own (they hold
1187+ /// the card);
11491188/// 2. the nodes' programs and artifacts — functions of the leaves' artifacts,
11501189/// not of any proof — on a thread of their own, beside
11511190/// 3. the leaves proved `siblings` at a time, each harvested without its
1152- /// verify;
1191+ /// verify: with `beside`, each leaf executes and fills its traces while the
1192+ /// artifacts are built (neither reads them) and waits for them only to
1193+ /// prove; without, it waits for every artifact first (the order before,
1194+ /// `W3_EXEC_BESIDE_ARTIFACTS=0`);
11531195/// 4. each level above proved over the harvested children.
11541196///
11551197/// Returns each level's timing and every proof with its artifacts, level by
@@ -1160,6 +1202,7 @@ fn prove_tree_pipelined(
11601202 proof : & BlockWhirProof ,
11611203 leaves : Vec < LfmProgram > ,
11621204 siblings : usize ,
1205+ beside : bool ,
11631206) -> Result <
11641207 (
11651208 Vec < LevelTiming > ,
@@ -1171,27 +1214,32 @@ fn prove_tree_pipelined(
11711214 let wrap = aggregation_wrap_options ( ) ;
11721215 let words = plan. child_layout ( ) . total ( ) ;
11731216 let t_level = std:: time:: Instant :: now ( ) ;
1174-
1175- // 1. the leaves' artifacts.
1176- let leaf_built: Vec < ( super :: registry:: LfmArtifacts , DerivedChild , f64 ) > = {
1177- use rayon:: prelude:: * ;
1178- leaves
1179- . par_iter ( )
1180- . map ( |program| -> Result < _ , String > {
1181- let t = std:: time:: Instant :: now ( ) ;
1182- let artifacts = artifacts_of ( program, & wrap) ;
1183- let derived = DerivedChild :: from_artifacts ( & artifacts, & wrap, words) ?;
1184- Ok ( ( artifacts, derived, t. elapsed ( ) . as_secs_f64 ( ) ) )
1185- } )
1186- . collect :: < Result < _ , String > > ( ) ?
1187- } ;
11881217 let shape = plan. levels ( ) ;
11891218 let mut timings = Vec :: with_capacity ( shape. len ( ) + 1 ) ;
11901219 let mut proofs = Vec :: new ( ) ;
1220+ let built: Published < Vec < ( super :: registry:: LfmArtifacts , DerivedChild , f64 ) > > =
1221+ Published :: new ( ) ;
11911222
11921223 let ( leaf_proved, nodes) = std:: thread:: scope ( |scope| {
1224+ // 1. the leaves' artifacts.
1225+ scope. spawn ( || {
1226+ use rayon:: prelude:: * ;
1227+ let guard = PublishGuard ( & built) ;
1228+ guard. publish (
1229+ leaves
1230+ . par_iter ( )
1231+ . map ( |program| -> Result < _ , String > {
1232+ let t = std:: time:: Instant :: now ( ) ;
1233+ let artifacts = artifacts_of ( program, & wrap) ;
1234+ let derived = DerivedChild :: from_artifacts ( & artifacts, & wrap, words) ?;
1235+ Ok ( ( artifacts, derived, t. elapsed ( ) . as_secs_f64 ( ) ) )
1236+ } )
1237+ . collect :: < Result < _ , String > > ( ) ,
1238+ ) ;
1239+ } ) ;
11931240 // 2. the nodes' programs, from the leaves' derived shapes.
11941241 let nodes = scope. spawn ( || -> Result < Vec < Vec < TreeNode > > , String > {
1242+ let leaf_built = built. wait ( ) ?;
11951243 let mut owned: Vec < Vec < TreeNode > > = Vec :: with_capacity ( shape. len ( ) ) ;
11961244 for ( lv, arities) in shape. iter ( ) . enumerate ( ) {
11971245 let top = lv + 1 == shape. len ( ) ;
@@ -1224,15 +1272,23 @@ fn prove_tree_pipelined(
12241272 // 3. the leaves, `siblings` at a time.
12251273 let proved = in_index_order ( leaves. len ( ) , siblings, |k| -> Result < _ , String > {
12261274 let arenas = block_leaf_arena ( plan, proof, k) ?;
1275+ if !beside {
1276+ built. wait ( ) ?;
1277+ }
12271278 let t = std:: time:: Instant :: now ( ) ;
1228- let lfm = lfm_prove ( & leaves[ k] , & leaf_built[ k] . 0 , & arenas, & wrap)
1279+ let filled = lfm_execute_and_fill ( & leaves[ k] , & arenas, crate :: hash_pin:: BLOCK_HASHER )
1280+ . map_err ( |e| format ! ( "leaf {k}: {e:?}" ) ) ?;
1281+ let artifacts = & built. wait ( ) ?[ k] . 0 ;
1282+ let lfm = filled
1283+ . prove ( artifacts, & wrap, decide_lfm_residency ( ) )
12291284 . map_err ( |e| format ! ( "leaf {k}: {e:?}" ) ) ?;
12301285 let prove = t. elapsed ( ) . as_secs_f64 ( ) ;
1231- let child = harvest_child ( leaf_built [ k ] . 0 . clone ( ) , wrap. clone ( ) , & lfm) ;
1286+ let child = harvest_child ( artifacts . clone ( ) , wrap. clone ( ) , & lfm) ;
12321287 Ok ( ( lfm, child, prove) )
12331288 } ) ;
12341289 ( proved, nodes. join ( ) )
12351290 } ) ;
1291+ let leaf_built = built. take ( ) ?;
12361292 let leaf_proved: Vec < ( LfmProof , RealChild , f64 ) > =
12371293 leaf_proved. into_iter ( ) . collect :: < Result < _ , String > > ( ) ?;
12381294 let nodes = nodes. map_err ( |_| "the node builder panicked" . to_string ( ) ) ??;
@@ -1324,6 +1380,9 @@ fn the_whir_block_tree_on_a_real_block() {
13241380 let leaves = knob ( "W3_LEAVES" ) ;
13251381 let siblings = knob ( "W3_SIBLINGS" ) . unwrap_or ( 3 ) ;
13261382 let fan_in = knob ( "W3_FAN_IN" ) . unwrap_or ( BLOCK_FAN_IN ) ;
1383+ // `W3_EXEC_BESIDE_ARTIFACTS=0|1` (default 1): the leaves execute and fill
1384+ // while their artifacts are built, or (0, the control) after all of them.
1385+ let beside = knob ( "W3_EXEC_BESIDE_ARTIFACTS" ) . is_none_or ( |v| v != 0 ) ;
13271386 // `BLOCK_WHIR_ARGUE=batched|per-table` (production: batched): each group's
13281387 // tables argued together, at the format's bin cap (`BLOCK_WHIR_ARGUE_CAP=k`
13291388 // for 2^k), or each on its own.
@@ -1556,9 +1615,12 @@ fn the_whir_block_tree_on_a_real_block() {
15561615 plan. prepared( ) . len( )
15571616 ) ;
15581617
1618+ // The readouts above run on the clock, between the base and the tree: a
1619+ // prover prints none of them, so the whole block is also given without.
1620+ let readouts = t0. elapsed ( ) . as_secs_f64 ( ) - base;
15591621 let t = std:: time:: Instant :: now ( ) ;
15601622 let ( timings, proofs) =
1561- prove_tree_pipelined ( & plan, & proof, programs, siblings) . expect ( "the tree proves" ) ;
1623+ prove_tree_pipelined ( & plan, & proof, programs, siblings, beside ) . expect ( "the tree proves" ) ;
15621624 let top = & proofs. last ( ) . expect ( "a top" ) . 1 ;
15631625 let tree = t. elapsed ( ) . as_secs_f64 ( ) ;
15641626 let whole = t0. elapsed ( ) . as_secs_f64 ( ) ;
@@ -1575,8 +1637,17 @@ fn the_whir_block_tree_on_a_real_block() {
15751637 ) ;
15761638 }
15771639 println ! (
1578- "W3 RECURSION: {:.2}s after the base (tree {tree:.2}s) · whole block {whole:.2}s" ,
1579- whole - base
1640+ "W3 RECURSION: {:.2}s after the base (tree {tree:.2}s) · whole block {whole:.2}s · whole excl. harness readouts {:.2}s (readouts {readouts:.2}s) · leaves execute beside their artifacts {beside}" ,
1641+ whole - base,
1642+ whole - readouts
1643+ ) ;
1644+ // The top proof's bytes: two runs prove the same ones under
1645+ // LAMBDA_VM_FIXED_TRACE_HASH=1 and LAMBDA_VM_DETERMINISTIC_GRIND=1.
1646+ let top_bytes = rkyv:: to_bytes :: < rkyv:: rancor:: Error > ( & top. proof ) . expect ( "serialize the top" ) ;
1647+ println ! (
1648+ "W3 TOP DIGEST: {} (blake3 of the top proof's {} bytes)" ,
1649+ & blake3:: hash( & top_bytes) . to_hex( ) [ ..32 ] ,
1650+ top_bytes. len( )
15801651 ) ;
15811652
15821653 // Off the clock: the harness's checks.
0 commit comments